@vincemakes/kiso-code 0.46.2 → 0.47.1
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/auth/oauth/chatgpt.js +25 -8
- package/dist/auth/oauth/page.d.ts +25 -0
- package/dist/auth/oauth/page.js +44 -0
- package/dist/chat.d.ts +12 -1
- package/dist/chat.js +126 -44
- package/dist/coding-prompt.d.ts +7 -0
- package/dist/coding-prompt.js +12 -4
- package/dist/create-coding-agent.js +11 -2
- package/dist/dispatch.d.ts +2 -0
- package/dist/dispatch.js +335 -78
- package/dist/git-branch.d.ts +17 -0
- package/dist/git-branch.js +69 -0
- package/dist/in-run-compaction.d.ts +54 -0
- package/dist/in-run-compaction.js +44 -0
- package/dist/index.js +101 -41
- package/dist/opening.d.ts +48 -0
- package/dist/opening.js +43 -0
- package/dist/replay.d.ts +8 -1
- package/dist/replay.js +22 -11
- package/dist/resume.js +21 -16
- package/dist/session-cards.d.ts +2 -0
- package/dist/session-cards.js +3 -3
- package/dist/settings.d.ts +19 -0
- package/dist/settings.js +34 -5
- package/dist/skill-invoke.d.ts +11 -0
- package/dist/skill-invoke.js +59 -0
- package/dist/state.d.ts +7 -0
- package/dist/task-notice.d.ts +33 -0
- package/dist/task-notice.js +73 -0
- package/dist/trust-ui.d.ts +7 -0
- package/dist/trust-ui.js +54 -9
- package/dist/window-title.d.ts +43 -23
- package/dist/window-title.js +78 -50
- package/package.json +14 -14
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphite §8.9 — the branch the status bar names.
|
|
3
|
+
*
|
|
4
|
+
* Read from `.git/HEAD`, walking up from the working directory — no git
|
|
5
|
+
* process is spawned, because the status bar paints on every idle frame.
|
|
6
|
+
* A `.git` FILE (a worktree, a submodule) points at the real git
|
|
7
|
+
* directory with `gitdir: <path>`, and that is followed. A detached HEAD
|
|
8
|
+
* shows its short sha. Outside a repository, or on any read error, there
|
|
9
|
+
* is no branch and the bar says nothing about one.
|
|
10
|
+
*
|
|
11
|
+
* The answer is cached per HEAD file and re-read only when HEAD's mtime
|
|
12
|
+
* moves — a checkout the model runs mid-session is picked up on the next
|
|
13
|
+
* paint, and an idle session reads one stat per paint.
|
|
14
|
+
*/
|
|
15
|
+
/** What a HEAD file's contents name: the branch, or a detached sha's head. */
|
|
16
|
+
export declare function branchOf(head: string): string | null;
|
|
17
|
+
export declare function currentBranch(dir: string): string | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphite §8.9 — the branch the status bar names.
|
|
3
|
+
*
|
|
4
|
+
* Read from `.git/HEAD`, walking up from the working directory — no git
|
|
5
|
+
* process is spawned, because the status bar paints on every idle frame.
|
|
6
|
+
* A `.git` FILE (a worktree, a submodule) points at the real git
|
|
7
|
+
* directory with `gitdir: <path>`, and that is followed. A detached HEAD
|
|
8
|
+
* shows its short sha. Outside a repository, or on any read error, there
|
|
9
|
+
* is no branch and the bar says nothing about one.
|
|
10
|
+
*
|
|
11
|
+
* The answer is cached per HEAD file and re-read only when HEAD's mtime
|
|
12
|
+
* moves — a checkout the model runs mid-session is picked up on the next
|
|
13
|
+
* paint, and an idle session reads one stat per paint.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, statSync } from "node:fs";
|
|
16
|
+
import { dirname, join, resolve } from "node:path";
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
/** The HEAD file of the repository `dir` is inside, or null. */
|
|
19
|
+
function headFile(dir) {
|
|
20
|
+
let d = resolve(dir);
|
|
21
|
+
for (;;) {
|
|
22
|
+
const dotGit = join(d, ".git");
|
|
23
|
+
try {
|
|
24
|
+
const st = statSync(dotGit);
|
|
25
|
+
if (st.isDirectory())
|
|
26
|
+
return join(dotGit, "HEAD");
|
|
27
|
+
if (st.isFile()) {
|
|
28
|
+
const m = /^gitdir:\s*(.+)$/m.exec(readFileSync(dotGit, "utf8"));
|
|
29
|
+
if (m !== null)
|
|
30
|
+
return join(resolve(d, m[1].trim()), "HEAD");
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// no .git here — keep walking
|
|
36
|
+
}
|
|
37
|
+
const up = dirname(d);
|
|
38
|
+
if (up === d)
|
|
39
|
+
return null;
|
|
40
|
+
d = up;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** What a HEAD file's contents name: the branch, or a detached sha's head. */
|
|
44
|
+
export function branchOf(head) {
|
|
45
|
+
const text = head.trim();
|
|
46
|
+
const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(text);
|
|
47
|
+
if (ref !== null)
|
|
48
|
+
return ref[1];
|
|
49
|
+
if (/^[0-9a-f]{7,64}$/i.test(text))
|
|
50
|
+
return text.slice(0, 7);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
export function currentBranch(dir) {
|
|
54
|
+
try {
|
|
55
|
+
const head = headFile(dir);
|
|
56
|
+
if (head === null)
|
|
57
|
+
return null;
|
|
58
|
+
const mtimeMs = statSync(head).mtimeMs;
|
|
59
|
+
const hit = cache.get(head);
|
|
60
|
+
if (hit !== undefined && hit.mtimeMs === mtimeMs)
|
|
61
|
+
return hit.branch;
|
|
62
|
+
const branch = branchOf(readFileSync(head, "utf8"));
|
|
63
|
+
cache.set(head, { mtimeMs, branch });
|
|
64
|
+
return branch;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compaction round (owner, 2026-10-06) — a summary call inside a run,
|
|
3
|
+
* as the screen sees it.
|
|
4
|
+
*
|
|
5
|
+
* The runtime tells the CLI when an in-run summary starts, how far its
|
|
6
|
+
* output has come, and how it ended (`contextPolicy.tiers.onSummary`). The
|
|
7
|
+
* running row reads `current()` on every repaint: while a summary is in
|
|
8
|
+
* flight it is the `compacting · auto` row `/compact` draws, not `working`
|
|
9
|
+
* with a clock that runs while nothing arrives. When the summary is kept,
|
|
10
|
+
* its sizes wait here for the `summarized` event, whose COMPACTED row says
|
|
11
|
+
* them (`takeKept`).
|
|
12
|
+
*/
|
|
13
|
+
import type { CompactingProgress } from "@vincemakes/kiso-tui";
|
|
14
|
+
export interface InRunCompaction {
|
|
15
|
+
/** when the summary call started (ms) */
|
|
16
|
+
readonly since: number;
|
|
17
|
+
/** the covered user rounds and their estimated tokens */
|
|
18
|
+
readonly rounds: number;
|
|
19
|
+
readonly tokens: number;
|
|
20
|
+
/** the latest report against the output budget; null before the first */
|
|
21
|
+
progress: CompactingProgress | null;
|
|
22
|
+
}
|
|
23
|
+
type SummaryEvent = {
|
|
24
|
+
readonly phase: "start";
|
|
25
|
+
readonly reason: string;
|
|
26
|
+
readonly info: {
|
|
27
|
+
readonly rounds: number;
|
|
28
|
+
readonly tokens: number;
|
|
29
|
+
};
|
|
30
|
+
} | {
|
|
31
|
+
readonly phase: "progress";
|
|
32
|
+
readonly progress: {
|
|
33
|
+
readonly produced: number;
|
|
34
|
+
readonly budget: number | null;
|
|
35
|
+
readonly reasoningUnseen: boolean;
|
|
36
|
+
};
|
|
37
|
+
} | {
|
|
38
|
+
readonly phase: "end";
|
|
39
|
+
readonly outcome: "kept" | "discarded" | "failed";
|
|
40
|
+
readonly pre: number;
|
|
41
|
+
readonly post: number;
|
|
42
|
+
};
|
|
43
|
+
/** The runtime's callback (create-coding-agent passes it to the tiers). */
|
|
44
|
+
export declare function onInRunSummary(event: SummaryEvent): void;
|
|
45
|
+
/** The summary in flight, or null — what the running row draws. */
|
|
46
|
+
export declare function current(): InRunCompaction | null;
|
|
47
|
+
/** The kept summary's sizes, once: the `summarized` event's row says them. */
|
|
48
|
+
export declare function takeKept(): {
|
|
49
|
+
readonly pre: number;
|
|
50
|
+
readonly post: number;
|
|
51
|
+
} | null;
|
|
52
|
+
/** A run that ends mid-summary (an abort) leaves nothing behind. */
|
|
53
|
+
export declare function clearInRunSummary(): void;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compaction round (owner, 2026-10-06) — a summary call inside a run,
|
|
3
|
+
* as the screen sees it.
|
|
4
|
+
*
|
|
5
|
+
* The runtime tells the CLI when an in-run summary starts, how far its
|
|
6
|
+
* output has come, and how it ended (`contextPolicy.tiers.onSummary`). The
|
|
7
|
+
* running row reads `current()` on every repaint: while a summary is in
|
|
8
|
+
* flight it is the `compacting · auto` row `/compact` draws, not `working`
|
|
9
|
+
* with a clock that runs while nothing arrives. When the summary is kept,
|
|
10
|
+
* its sizes wait here for the `summarized` event, whose COMPACTED row says
|
|
11
|
+
* them (`takeKept`).
|
|
12
|
+
*/
|
|
13
|
+
let live = null;
|
|
14
|
+
let kept = null;
|
|
15
|
+
/** The runtime's callback (create-coding-agent passes it to the tiers). */
|
|
16
|
+
export function onInRunSummary(event) {
|
|
17
|
+
if (event.phase === "start") {
|
|
18
|
+
live = { since: Date.now(), rounds: event.info.rounds, tokens: event.info.tokens, progress: null };
|
|
19
|
+
kept = null;
|
|
20
|
+
}
|
|
21
|
+
else if (event.phase === "progress") {
|
|
22
|
+
if (live !== null)
|
|
23
|
+
live.progress = { produced: event.progress.produced, budget: event.progress.budget, reasoningUnseen: event.progress.reasoningUnseen };
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
live = null;
|
|
27
|
+
kept = event.outcome === "kept" ? { pre: event.pre, post: event.post } : null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** The summary in flight, or null — what the running row draws. */
|
|
31
|
+
export function current() {
|
|
32
|
+
return live;
|
|
33
|
+
}
|
|
34
|
+
/** The kept summary's sizes, once: the `summarized` event's row says them. */
|
|
35
|
+
export function takeKept() {
|
|
36
|
+
const k = kept;
|
|
37
|
+
kept = null;
|
|
38
|
+
return k;
|
|
39
|
+
}
|
|
40
|
+
/** A run that ends mid-summary (an abort) leaves nothing behind. */
|
|
41
|
+
export function clearInRunSummary() {
|
|
42
|
+
live = null;
|
|
43
|
+
kept = null;
|
|
44
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -30,9 +30,9 @@ import { createInterface } from "node:readline";
|
|
|
30
30
|
import { Readable } from "node:stream";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
import { basename, join } from "node:path";
|
|
33
|
-
import { Body, Editor, PROMPT, bannerLines, currentGround, resolveGround, setGround, escapeTerminal, extensionsBannerText,
|
|
33
|
+
import { Body, Editor, PROMPT, bannerLines, currentGround, currentGroundRgb, parseOscColor, resolveGround, setGround, escapeTerminal, extensionsBannerText, extensionsFact, idColumn, interactivePrompt, palette, renderSessionLine, sessionListFooter, sessionListHeader, sessionListRow, sessionListUnknownLine, slashCommandNames } from "@vincemakes/kiso-tui";
|
|
34
34
|
import { disposeExtensions, SessionStore } from "@vincemakes/kiso-runtime";
|
|
35
|
-
import { listSessionSidecars, migrateSummaries, readProfile, summaryMigrationPending } from "@vincemakes/kiso-runtime/internal";
|
|
35
|
+
import { listSessionSidecars, migrateSummaries, readProfile, readSessionName, summaryMigrationPending } from "@vincemakes/kiso-runtime/internal";
|
|
36
36
|
import { skillMenuItems } from "./skill-invoke.js";
|
|
37
37
|
import { canonicalPath, hasSession, locateSession, projectLayoutActive, sessionFolders } from "./projects.js";
|
|
38
38
|
import { reverseMigration } from "./session-migration.js";
|
|
@@ -43,15 +43,18 @@ import { isProtectedPath, protectedIdentity, PROTECTED_REFUSAL } from "@vincemak
|
|
|
43
43
|
import { OFFERED_MODES, applyModeState, envModeLayer, getDontAsk, modeDisplay, parseMode, resolveModeLayers } from "./mode.js";
|
|
44
44
|
import { activeStoreDir, setActiveStoreDir, agentModel, atFiles, body, bodyLog, kisoHome, workspaceRoot, projectRoot, ownSessionsDir, setOpenSessionFolder, builtInExtensions, currentFaux, dock, loadedExtensions, mergedConfig, mergedTempPaths, modelChoice, projectExtensions, configModels, configuredWindow, agentBaseUrl, currentModelName, currentAgentExtensions, sessionStoreRef, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setCurrentProfileName, setExtensionLists, protectedFiles, setMergedConfig, setModelChoice, setSessionStore, userExtensions, VERSION, lastBinding, acceptDrift, setAcceptDrift, loadedSkillsCatalog, queuedSwitchLines } from "./state.js";
|
|
45
45
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
46
|
-
import { chat, contextWindowTokens, displayCtxRatio, microcompactThresholdFor, statusModelLabel } from "./chat.js";
|
|
46
|
+
import { barFor, chat, contextWindowTokens, displayCtxRatio, microcompactThresholdFor, statusModelLabel } from "./chat.js";
|
|
47
47
|
import { preferences, usePreferences } from "./preferences.js";
|
|
48
|
-
import { settingsLayers, setChildTurnBudget, stopAllTasks,
|
|
48
|
+
import { settingsLayers, setChildTurnBudget, stopAllTasks, tasksFor } from "./state.js";
|
|
49
|
+
import { tasksForDisplay } from "./task-notice.js";
|
|
49
50
|
import { finishChild } from "./child-result.js";
|
|
50
51
|
import { loadUserConfig, resolveAutoCompact } from "./config.js";
|
|
51
52
|
import { checkForUpdate, knownUpdate, updateCardLines } from "./update-check.js";
|
|
52
53
|
import { tmuxMouseHint } from "./tmux-hint.js";
|
|
53
54
|
import { resume } from "./resume.js";
|
|
54
|
-
import { paintWindowTitle } from "./window-title.js";
|
|
55
|
+
import { paintWindowTitle, setTitleName, setTitleState } from "./window-title.js";
|
|
56
|
+
import { projectInstructions } from "./coding-prompt.js";
|
|
57
|
+
import { openingFacts } from "./opening.js";
|
|
55
58
|
import { resumeTail } from "./resume-tail.js";
|
|
56
59
|
import { replayInto } from "./replay.js";
|
|
57
60
|
import { armByteTrace } from "./byte-trace.js";
|
|
@@ -316,6 +319,15 @@ function editorInput(editor) {
|
|
|
316
319
|
panelCancel() {
|
|
317
320
|
editor.cancelPanel();
|
|
318
321
|
},
|
|
322
|
+
// Graphite R3e: a read-only sheet over the input (`/status`,
|
|
323
|
+
// `/context`, `/skills`)
|
|
324
|
+
openSheet(rows) {
|
|
325
|
+
editor.openSheet(rows);
|
|
326
|
+
},
|
|
327
|
+
// the sheets round: `/help` opens the command list
|
|
328
|
+
openCommands() {
|
|
329
|
+
editor.openCommands();
|
|
330
|
+
},
|
|
319
331
|
// TUI2-R2 ②: the session picker — the editor owns the keys (the
|
|
320
332
|
// selection walk, the filter, enter/esc), the compositor draws the
|
|
321
333
|
// band, and the id comes back here.
|
|
@@ -410,20 +422,30 @@ function makeLineInput() {
|
|
|
410
422
|
let osc;
|
|
411
423
|
let colorScheme;
|
|
412
424
|
const theme = () => process.env.KISO_THEME ?? userTheme;
|
|
425
|
+
const sameRgb = (a, b) => a === b || (a !== null && b !== null && a.r === b.r && a.g === b.g && a.b === b.b);
|
|
413
426
|
const rewalk = () => {
|
|
414
427
|
const next = resolveGround({ theme: theme(), colorScheme, osc, colorfgbg: process.env.COLORFGBG });
|
|
428
|
+
// Graphite §3.4: the colour the terminal reported rides along,
|
|
429
|
+
// whichever rung decided the ground — the surfaces are derived
|
|
430
|
+
// from it when it is of the same kind (graphite.ts decides).
|
|
431
|
+
const rgb = osc === undefined ? null : parseOscColor(osc);
|
|
415
432
|
// DC-3/DC-14's model: the first frame never waits for a reply.
|
|
416
433
|
// A reply that changes nothing repaints nothing — which is also
|
|
417
434
|
// why two answers that AGREE cost one repaint and not two.
|
|
418
|
-
if (next === currentGround())
|
|
435
|
+
if (next === currentGround() && sameRgb(rgb, currentGroundRgb()))
|
|
419
436
|
return;
|
|
420
|
-
setGround(next);
|
|
437
|
+
setGround(next, rgb);
|
|
421
438
|
body.onGroundChange();
|
|
422
439
|
};
|
|
423
440
|
setGround(resolveGround({ theme: theme(), colorfgbg: process.env.COLORFGBG }));
|
|
424
441
|
// the reply, when there is one, re-walks the ladder WITH it — and
|
|
425
442
|
// `theme` goes in first again, so an explicit answer still wins.
|
|
426
443
|
editor.onOsc((reply) => {
|
|
444
|
+
// Only the answer to the question asked (OSC 11, the background
|
|
445
|
+
// colour) is the ground: any other OSC a terminal sends would
|
|
446
|
+
// otherwise overwrite it.
|
|
447
|
+
if (!reply.startsWith("11;"))
|
|
448
|
+
return;
|
|
427
449
|
osc = reply;
|
|
428
450
|
rewalk();
|
|
429
451
|
});
|
|
@@ -455,6 +477,9 @@ function makeLineInput() {
|
|
|
455
477
|
// OR-11 (a): ONE literal. The compositor draws this lead and the
|
|
456
478
|
// editor measures its rows against it; two copies is how they came
|
|
457
479
|
// to disagree by two columns in the first place.
|
|
480
|
+
// Graphite §7.8 (owner, 2026-09-29): no prompt glyph — R2's ruling
|
|
481
|
+
// stands again after R1c's `›`; the caret is the terminal's own
|
|
482
|
+
// ink, at column 0, as in 0.44.
|
|
458
483
|
const COMPOSER_LEAD = "";
|
|
459
484
|
dock.bindInput(() => editor.dockState(), COMPOSER_LEAD);
|
|
460
485
|
// …and BOTH renderers are live: the dock draws this row while it is
|
|
@@ -470,7 +495,7 @@ function makeLineInput() {
|
|
|
470
495
|
editor.bindAtItems(atFiles); // KC3 §5: the file source — listed per OPEN
|
|
471
496
|
dock.bindAt(() => editor.atState()); // KC3 §4: the picker's band
|
|
472
497
|
dock.bindApproval(() => editor.panelState()); // W21: the panel's bound state
|
|
473
|
-
dock.bindSheet(() => editor.
|
|
498
|
+
dock.bindSheet(() => editor.sheetContent()); // TUI2-R1 (D): the ? keys sheet; R3e: /status
|
|
474
499
|
dock.bindPick(() => editor.pickState()); // TUI2-R2 ②: the resume picker's band
|
|
475
500
|
// R5: the transcript viewer. The editor reports whether it is up and
|
|
476
501
|
// forwards the commands; the STATE lives in the compositor, because
|
|
@@ -496,11 +521,15 @@ function bannerExtensionText() {
|
|
|
496
521
|
// terminal layer (extensionsBannerText — a pure function of the three
|
|
497
522
|
// name lists, including the "(connecting…)" in-flight label). Which
|
|
498
523
|
// lists exist is the CLI's fact and stays here.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
524
|
+
return extensionsBannerText(...extensionLists());
|
|
525
|
+
}
|
|
526
|
+
/** The three lists both forms are built from — the pipe's line and the
|
|
527
|
+
* opening's EXTENSIONS row. 0.40.0: with the don't-ask switch on the ask
|
|
528
|
+
* extension is loaded but offers no tool (builtin.ts offInDontAsk) — the
|
|
529
|
+
* banner says so, rather than listing it as if it could ask. */
|
|
530
|
+
function extensionLists() {
|
|
502
531
|
const builtIn = builtInExtensions.map((e) => (e.name === "ask" && getDontAsk() ? { name: e.name, note: "off in dontAsk" } : e));
|
|
503
|
-
return
|
|
532
|
+
return [builtIn, userExtensions, projectExtensions];
|
|
504
533
|
}
|
|
505
534
|
/** E1: the startup banner line(s) — TTY: logo + merged extensions + the
|
|
506
535
|
* W5 resume list as a LIVE banner cell (W1: the tier re-derives on
|
|
@@ -549,19 +578,15 @@ async function announceUpdate() {
|
|
|
549
578
|
// did, a version check is not a reason to disturb a session.
|
|
550
579
|
}
|
|
551
580
|
}
|
|
552
|
-
function extensionsBanner(
|
|
581
|
+
function extensionsBanner(sessionId, resumedEvents = 0) {
|
|
553
582
|
const text = bannerExtensionText();
|
|
554
583
|
if (!process.stdout.isTTY) {
|
|
555
584
|
if (text !== "")
|
|
556
585
|
bodyLog(`${text}\n`);
|
|
557
586
|
return;
|
|
558
587
|
}
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
// (one source, so the two can never disagree); the workspace is the
|
|
562
|
-
// home-relative cwd, because `~/Desktop/devv/kiso` is what a human
|
|
563
|
-
// calls the place and `/Users/vinve/Desktop/devv/kiso` is what a
|
|
564
|
-
// filesystem calls it.
|
|
588
|
+
// Graphite §7.10: the opening states what loaded. The model, the mode
|
|
589
|
+
// and the folder are the status bar's now (§8.9) and are not repeated.
|
|
565
590
|
const home = homedir();
|
|
566
591
|
const cwd = process.cwd();
|
|
567
592
|
/** DC-49: realpath, falling back to the raw path when it cannot be
|
|
@@ -575,15 +600,26 @@ function extensionsBanner(resume = []) {
|
|
|
575
600
|
return dir;
|
|
576
601
|
}
|
|
577
602
|
};
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
603
|
+
const skills = loadedSkillsCatalog();
|
|
604
|
+
const mcp = loadedExtensions.find((e) => e.name === "mcp");
|
|
605
|
+
body.banner(VERSION, text.replace(/^ · /, ""), [], {
|
|
606
|
+
resumed: resumedEvents > 0,
|
|
607
|
+
facts: openingFacts({
|
|
608
|
+
// the last sweep: the new rows are the dock's; off one the
|
|
609
|
+
// `session <id>` and `[faux mode — …]` lines still print
|
|
610
|
+
sessionId: dock.active ? sessionId : null,
|
|
611
|
+
resumedEvents,
|
|
612
|
+
faux: dock.active && currentFaux,
|
|
613
|
+
rules: projectInstructions(cwd, protectedFiles())?.name ?? null,
|
|
614
|
+
skills: skills === null ? null : { count: skills.entries.length, broken: skills.broken.length },
|
|
615
|
+
mcp: mcp === undefined ? null : { tools: (mcp.tools ?? []).map((t) => t.name), connecting: mcp.connecting === true },
|
|
616
|
+
extensions: dock.active ? extensionsFact(...extensionLists()) : text === "" ? null : { value: text.replace(/^ · /, "") },
|
|
617
|
+
// DC-49 — REALPATH on both sides. A symlinked HOME (or a symlinked
|
|
618
|
+
// cwd) compares unequal as raw strings while being the same
|
|
619
|
+
// directory, and the row would then be absent exactly where it is
|
|
620
|
+
// most needed.
|
|
621
|
+
homeWorkspace: realOf(cwd) === realOf(home),
|
|
622
|
+
}),
|
|
587
623
|
});
|
|
588
624
|
}
|
|
589
625
|
export { workspaceRoot } from "./state.js";
|
|
@@ -814,9 +850,10 @@ function bindRestoredSession(session) {
|
|
|
814
850
|
function paintBootStatus(session) {
|
|
815
851
|
if (!dock.active)
|
|
816
852
|
return;
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
|
|
853
|
+
// Graphite §8.9: the boot row is the status bar — the same builder the
|
|
854
|
+
// idle and running rows use (it used to drop `floor off`, the one fact
|
|
855
|
+
// the boot row most needs when it is true).
|
|
856
|
+
dock.setBar(barFor(session));
|
|
820
857
|
}
|
|
821
858
|
/** PH-1a (finding PH-F12): a usage error raised from inside the TUI —
|
|
822
859
|
* main's finally still runs (dock teardown, agent close, temp cleanup)
|
|
@@ -933,7 +970,7 @@ async function reloadAgent(old, id, input, announce = true) {
|
|
|
933
970
|
delete process.env.KISO_DELEGATION_CONFIG_JSON;
|
|
934
971
|
else
|
|
935
972
|
process.env.KISO_DELEGATION_CONFIG_JSON = oldCfg.delegation;
|
|
936
|
-
|
|
973
|
+
body.notice(`[reload] ${err instanceof Error ? err.message : String(err)} — nothing changed, the previous set is still in force`);
|
|
937
974
|
return old;
|
|
938
975
|
}
|
|
939
976
|
// The new set is built and sound; only now does the old one go.
|
|
@@ -948,7 +985,7 @@ async function reloadAgent(old, id, input, announce = true) {
|
|
|
948
985
|
}
|
|
949
986
|
}
|
|
950
987
|
if (announce)
|
|
951
|
-
|
|
988
|
+
body.notice(`[reload] ${loadedExtensions.length} extensions, ${skillCount()} skills — the conversation is unchanged`);
|
|
952
989
|
return next;
|
|
953
990
|
}
|
|
954
991
|
async function chatLoop(first, firstId, input, autoCompact) {
|
|
@@ -1038,7 +1075,11 @@ async function chatLoop(first, firstId, input, autoCompact) {
|
|
|
1038
1075
|
refused = false; // back in the session that was already on screen
|
|
1039
1076
|
}
|
|
1040
1077
|
else if (prev === null) {
|
|
1041
|
-
|
|
1078
|
+
// the last sweep (owner, 2026-10-06): on the terminal the opening's
|
|
1079
|
+
// SESSION row names the id — this line stood above the opening at
|
|
1080
|
+
// column 0. A pipe keeps it.
|
|
1081
|
+
if (!dock.active)
|
|
1082
|
+
bodyLog(`session ${id}\n`);
|
|
1042
1083
|
// REL-0152-D5: a session with history says what that history WAS.
|
|
1043
1084
|
// Resuming used to print this one line and drop you at an empty
|
|
1044
1085
|
// prompt inside a conversation with thousands of events — the
|
|
@@ -1053,7 +1094,7 @@ async function chatLoop(first, firstId, input, autoCompact) {
|
|
|
1053
1094
|
// recent sessions just to draw a badge, and `agent.session()`
|
|
1054
1095
|
// throws on profile drift — so one drifted session anywhere in the
|
|
1055
1096
|
// history stopped kiso from starting at all.
|
|
1056
|
-
extensionsBanner();
|
|
1097
|
+
extensionsBanner(id, session.log.all.length);
|
|
1057
1098
|
// OR-9 (owner, 2026-09-09): the update card under the banner, at
|
|
1058
1099
|
// EVERY start while a newer version is known — §7.10's once-only
|
|
1059
1100
|
// line is superseded. What the cache knows is painted NOW, with
|
|
@@ -1075,7 +1116,7 @@ async function chatLoop(first, firstId, input, autoCompact) {
|
|
|
1075
1116
|
void announceUpdate();
|
|
1076
1117
|
}
|
|
1077
1118
|
else {
|
|
1078
|
-
|
|
1119
|
+
body.notice(`session ${id} (switched — previous: ${prev}, /resume ${prev} returns)\n`);
|
|
1079
1120
|
showResumeTail(session.log.all, session.id);
|
|
1080
1121
|
if (currentFaux)
|
|
1081
1122
|
session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
|
|
@@ -1105,7 +1146,9 @@ async function chatLoop(first, firstId, input, autoCompact) {
|
|
|
1105
1146
|
// lines above are here: this is the one step all three entry points
|
|
1106
1147
|
// share (first start, `/resume <id>`, a switch), so a tab can never
|
|
1107
1148
|
// be left naming the session the user just left.
|
|
1108
|
-
|
|
1149
|
+
// Graphite R3d: the session's own name, when the person gave it one
|
|
1150
|
+
setTitleName(readSessionName(activeStoreDir, session.id));
|
|
1151
|
+
paintWindowTitle();
|
|
1109
1152
|
const nav = {
|
|
1110
1153
|
// 0.40.0 dogfood: the ids only — agent.sessions() read every log whole
|
|
1111
1154
|
// (seconds on the owner's 118 sessions) before /resume could open
|
|
@@ -1190,9 +1233,10 @@ async function readSecret(prompt) {
|
|
|
1190
1233
|
*/
|
|
1191
1234
|
function showResumeTail(events, sessionId) {
|
|
1192
1235
|
const W = process.stdout.columns ?? 80;
|
|
1193
|
-
//
|
|
1236
|
+
// the tasks round: a task notice in the history reads its task's journal
|
|
1237
|
+
// (Amendment 8: a lost task's row names what it ran — from the same list)
|
|
1194
1238
|
if (dock.active)
|
|
1195
|
-
replayInto(body, events, W,
|
|
1239
|
+
replayInto(body, events, W, tasksForDisplay(tasksFor(sessionId)));
|
|
1196
1240
|
else
|
|
1197
1241
|
bodyLog(resumeTail(events, W).join("\n"));
|
|
1198
1242
|
}
|
|
@@ -1509,6 +1553,10 @@ async function main() {
|
|
|
1509
1553
|
const id = arg ?? newSessionId(sessionsDir());
|
|
1510
1554
|
// v2b: the dock (TTY only) wraps the whole session — the
|
|
1511
1555
|
// trust question, the banner, the body, and the input line.
|
|
1556
|
+
// the tasks round (owner, 2026-10-06): the first frames, before the
|
|
1557
|
+
// bar is bound, offer no key ladder (§7.8 retired it) — set while
|
|
1558
|
+
// the dock is not yet up, so nothing is drawn until enter() paints
|
|
1559
|
+
dock.setStatus("", "");
|
|
1512
1560
|
dock.enter();
|
|
1513
1561
|
// E area: a resumed session continues the script at its durable
|
|
1514
1562
|
// position — never restarts it (fauxSkip).
|
|
@@ -1533,6 +1581,10 @@ async function main() {
|
|
|
1533
1581
|
// argv[4] is the optional prompt; argv[3] is the session id
|
|
1534
1582
|
// (argv = [node, script, resume, id, prompt?]).
|
|
1535
1583
|
const prompt = process.argv[4];
|
|
1584
|
+
// the tasks round (owner, 2026-10-06): the first frames, before the
|
|
1585
|
+
// bar is bound, offer no key ladder (§7.8 retired it) — set while
|
|
1586
|
+
// the dock is not yet up, so nothing is drawn until enter() paints
|
|
1587
|
+
dock.setStatus("", "");
|
|
1536
1588
|
dock.enter();
|
|
1537
1589
|
const routeLine = arg !== undefined ? enterRouted(arg) : null;
|
|
1538
1590
|
agent = await createCodingAgent(arg, input, modelFlag);
|
|
@@ -1787,8 +1839,9 @@ async function main() {
|
|
|
1787
1839
|
// stopped, so a new user could never learn from the tool
|
|
1788
1840
|
// itself how to hand it a key.
|
|
1789
1841
|
const p = palette();
|
|
1790
|
-
console.log(
|
|
1791
|
-
|
|
1842
|
+
console.log(
|
|
1843
|
+
// Graphite §7.10: the opening's head says the tagline already
|
|
1844
|
+
`${p.dim}${bannerLines(80, process.stdout.rows ?? 0, VERSION, "").join("\n")}${p.reset}\n\n` +
|
|
1792
1845
|
" kiso [sessionId] interactive session (default command)\n" +
|
|
1793
1846
|
" kiso chat [sessionId] same as above\n" +
|
|
1794
1847
|
" kiso resume pick a session to continue (TTY picker)\n" +
|
|
@@ -1819,6 +1872,10 @@ async function main() {
|
|
|
1819
1872
|
// A area: no subcommand (or any non-command first argument) IS
|
|
1820
1873
|
// chat — the first argument is the session id.
|
|
1821
1874
|
const id = command ?? newSessionId(sessionsDir());
|
|
1875
|
+
// the tasks round (owner, 2026-10-06): the first frames, before the
|
|
1876
|
+
// bar is bound, offer no key ladder (§7.8 retired it) — set while
|
|
1877
|
+
// the dock is not yet up, so nothing is drawn until enter() paints
|
|
1878
|
+
dock.setStatus("", "");
|
|
1822
1879
|
dock.enter();
|
|
1823
1880
|
// R-I-p2 (finding R-I-p-2): the bare command passes the SAME
|
|
1824
1881
|
// input source and model flag as chat/resume — the pre-patch
|
|
@@ -1857,6 +1914,9 @@ async function main() {
|
|
|
1857
1914
|
// scroll region, the cursor lands at the input line, no broken
|
|
1858
1915
|
// terminal (kill -9 excepted; `reset` saves it).
|
|
1859
1916
|
dock.exit();
|
|
1917
|
+
// Graphite §8.10: a closed session never leaves a working or
|
|
1918
|
+
// waiting mark in the tab
|
|
1919
|
+
setTitleState("ready");
|
|
1860
1920
|
for (const id of tasksAtExit.unconfirmed)
|
|
1861
1921
|
console.error(`${id}: stop unconfirmed — it may still be running`);
|
|
1862
1922
|
if (tasksAtExit.left.length > 0)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphite §7.10 — what the opening says loaded: the session, the
|
|
3
|
+
* project's rules, the skills, the MCP servers, and the extensions. The
|
|
4
|
+
* model, the mode and the folder are the status bar's (§8.9) and are not
|
|
5
|
+
* repeated here — except the faux model, which the bar names but cannot
|
|
6
|
+
* say how to leave.
|
|
7
|
+
*
|
|
8
|
+
* Pure over what the CLI already holds at startup; the banner lays the
|
|
9
|
+
* facts out and knows nothing of where they came from.
|
|
10
|
+
*/
|
|
11
|
+
import type { BannerFact } from "@vincemakes/kiso-tui";
|
|
12
|
+
export interface OpeningInputs {
|
|
13
|
+
/** The session's id — the SESSION row names it (the last sweep: the
|
|
14
|
+
* `session <id>` line above the opening retired on a dock). Null off a
|
|
15
|
+
* dock, where that line still prints: the row keeps its old form. */
|
|
16
|
+
readonly sessionId: string | null;
|
|
17
|
+
/** The events a resumed session opened with; 0 for a new one. */
|
|
18
|
+
readonly resumedEvents: number;
|
|
19
|
+
/** No model resolved and on a dock: the scripted faux model answers
|
|
20
|
+
* (off a dock the `[faux mode — …]` line still prints). */
|
|
21
|
+
readonly faux: boolean;
|
|
22
|
+
/** The instruction file the prompt reads (`projectInstructions`), or null. */
|
|
23
|
+
readonly rules: string | null;
|
|
24
|
+
/** The skills catalog's size, or null when no skills extension loaded. */
|
|
25
|
+
readonly skills: {
|
|
26
|
+
readonly count: number;
|
|
27
|
+
readonly broken: number;
|
|
28
|
+
} | null;
|
|
29
|
+
/** The MCP extension's tools as they stand, or null when it is not loaded. */
|
|
30
|
+
readonly mcp: {
|
|
31
|
+
readonly tools: readonly string[];
|
|
32
|
+
readonly connecting: boolean;
|
|
33
|
+
} | null;
|
|
34
|
+
/** The extensions — `extensionsFact`'s count and names, from the SAME
|
|
35
|
+
* three lists the pipe's `[N extensions: …]` line is built from (so the
|
|
36
|
+
* two still cannot disagree about what loaded, and the 0.40.0 `ask (off
|
|
37
|
+
* in dontAsk)` note stays beside the tier that turns it off); null when
|
|
38
|
+
* nothing loaded. The last sweep (owner, 2026-10-06): the row carried
|
|
39
|
+
* the pipe's bracketed line verbatim — as it still does off a dock
|
|
40
|
+
* (`{ value: <the line> }`). */
|
|
41
|
+
readonly extensions: {
|
|
42
|
+
readonly value: string;
|
|
43
|
+
readonly note?: string;
|
|
44
|
+
} | null;
|
|
45
|
+
/** DC-49 — the workspace is the home directory. */
|
|
46
|
+
readonly homeWorkspace: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function openingFacts(o: OpeningInputs): BannerFact[];
|
package/dist/opening.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphite §7.10 — what the opening says loaded: the session, the
|
|
3
|
+
* project's rules, the skills, the MCP servers, and the extensions. The
|
|
4
|
+
* model, the mode and the folder are the status bar's (§8.9) and are not
|
|
5
|
+
* repeated here — except the faux model, which the bar names but cannot
|
|
6
|
+
* say how to leave.
|
|
7
|
+
*
|
|
8
|
+
* Pure over what the CLI already holds at startup; the banner lays the
|
|
9
|
+
* facts out and knows nothing of where they came from.
|
|
10
|
+
*/
|
|
11
|
+
const plural = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
|
|
12
|
+
export function openingFacts(o) {
|
|
13
|
+
const facts = [];
|
|
14
|
+
if (o.sessionId === null)
|
|
15
|
+
facts.push(o.resumedEvents > 0 ? { label: "SESSION", value: "resumed", note: plural(o.resumedEvents, "event") } : { label: "SESSION", value: "new", note: "resumable after kill -9" });
|
|
16
|
+
else
|
|
17
|
+
facts.push({ label: "SESSION", value: o.sessionId, note: o.resumedEvents > 0 ? `resumed \u00b7 ${plural(o.resumedEvents, "event")}` : "new \u00b7 resumable after kill -9" });
|
|
18
|
+
// the last sweep: no model resolved — the bar says `faux`, and this row
|
|
19
|
+
// says how to leave it (the line that said so was painted over at once)
|
|
20
|
+
// (as short as the RULES note: beside the wordmark a longer note is cut)
|
|
21
|
+
if (o.faux)
|
|
22
|
+
facts.push({ label: "MODEL", value: "faux", note: "set an API key, or add a model to config.json" });
|
|
23
|
+
facts.push(o.rules !== null ? { label: "RULES", value: o.rules } : { label: "RULES", value: "none", note: "an AGENTS.md or CLAUDE.md here is read" });
|
|
24
|
+
if (o.skills !== null) {
|
|
25
|
+
facts.push({ label: "SKILLS", value: String(o.skills.count), note: o.skills.broken > 0 ? `${o.skills.broken} cannot load · /skills` : "/skills lists them" });
|
|
26
|
+
}
|
|
27
|
+
if (o.mcp !== null) {
|
|
28
|
+
// the status tool is the extension's own, not a server's
|
|
29
|
+
const served = o.mcp.tools.filter((t) => t.startsWith("mcp__") && t !== "mcp__status");
|
|
30
|
+
const servers = new Set(served.map((t) => /^mcp__(.+?)__/.exec(t)?.[1] ?? t)).size;
|
|
31
|
+
if (servers === 0)
|
|
32
|
+
facts.push({ label: "MCP", value: o.mcp.connecting ? "connecting…" : "none" });
|
|
33
|
+
else
|
|
34
|
+
facts.push({ label: "MCP", value: plural(servers, "server"), note: `${plural(served.length, "tool")}${o.mcp.connecting ? " · connecting…" : ""}` });
|
|
35
|
+
}
|
|
36
|
+
if (o.extensions !== null)
|
|
37
|
+
facts.push({ label: "EXTENSIONS", value: o.extensions.value, ...(o.extensions.note !== undefined ? { note: o.extensions.note } : {}) });
|
|
38
|
+
// DC-49: STATES the fact and names the remedy; it does not warn — the
|
|
39
|
+
// configuration is allowed (owner, 2026-09-06)
|
|
40
|
+
if (o.homeWorkspace)
|
|
41
|
+
facts.push({ label: "", value: "home directory as workspace — cd into a project to narrow it" });
|
|
42
|
+
return facts;
|
|
43
|
+
}
|
package/dist/replay.d.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* summary (what the model sees next) and the turns it covers, read in
|
|
20
20
|
* the viewer — then any uncovered earlier turns folded as above.
|
|
21
21
|
*/
|
|
22
|
+
import type { TaskInfo } from "@vincemakes/kiso-runtime/internal";
|
|
23
|
+
import type { NoticeMark } from "@vincemakes/kiso-tui";
|
|
22
24
|
/** The Body surface the replay drives — the live run's own mutations. */
|
|
23
25
|
export interface ReplayBody {
|
|
24
26
|
userLine(text: string): void;
|
|
@@ -34,6 +36,11 @@ export interface ReplayBody {
|
|
|
34
36
|
untimed?: boolean;
|
|
35
37
|
}): void;
|
|
36
38
|
notice(text: string): void;
|
|
39
|
+
metaNotice(text: string, rows: readonly {
|
|
40
|
+
readonly label: string;
|
|
41
|
+
readonly sentence: string;
|
|
42
|
+
readonly mark?: NoticeMark;
|
|
43
|
+
}[]): void;
|
|
37
44
|
endTurn(thoughtSeconds: number): void;
|
|
38
45
|
fold(label: string, replay: () => void, summary?: string | null): void;
|
|
39
46
|
raw(lines: string[]): void;
|
|
@@ -49,5 +56,5 @@ export declare function turnsOf(events: readonly Ev[]): Ev[][];
|
|
|
49
56
|
* Replay a session's durable events into the body. Returns the number of
|
|
50
57
|
* turns it found (0: a fresh session — nothing is drawn).
|
|
51
58
|
*/
|
|
52
|
-
export declare function replayInto(body: ReplayBody, events: readonly Ev[], W?: number,
|
|
59
|
+
export declare function replayInto(body: ReplayBody, events: readonly Ev[], W?: number, tasks?: readonly TaskInfo[]): number;
|
|
53
60
|
export {};
|