@vincemakes/kiso-code 0.46.1 → 0.47.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/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 +133 -46
- package/dist/coding-prompt.d.ts +7 -0
- package/dist/coding-prompt.js +12 -4
- package/dist/config.d.ts +7 -3
- package/dist/config.js +39 -4
- package/dist/create-coding-agent.js +16 -6
- package/dist/dispatch.d.ts +2 -0
- package/dist/dispatch.js +337 -83
- package/dist/floor.d.ts +21 -0
- package/dist/floor.js +35 -35
- 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 +106 -45
- package/dist/opening.d.ts +48 -0
- package/dist/opening.js +43 -0
- package/dist/protected-writes.d.ts +8 -0
- package/dist/protected-writes.js +12 -0
- package/dist/remote-boundary.d.ts +55 -0
- package/dist/remote-boundary.js +217 -0
- package/dist/replay.d.ts +8 -1
- package/dist/replay.js +22 -10
- 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 +14 -3
- package/dist/state.js +29 -3
- package/dist/task-notice.d.ts +48 -7
- package/dist/task-notice.js +123 -13
- 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
package/dist/floor.d.ts
CHANGED
|
@@ -63,6 +63,27 @@ export declare function innerLines(argv: readonly LooseWord[], home: string): {
|
|
|
63
63
|
readonly lines: readonly string[];
|
|
64
64
|
readonly subshell: boolean;
|
|
65
65
|
} | null;
|
|
66
|
+
/** How deep command lines inside command lines are followed — `sh -c`,
|
|
67
|
+
* `eval` — and how many are read in all: eval's two readings would
|
|
68
|
+
* otherwise double at every level. */
|
|
69
|
+
export declare const MAX_INNER_DEPTH = 16;
|
|
70
|
+
export declare const MAX_INNER_LINES = 256;
|
|
71
|
+
/** git's global options, read to the subcommand. Shared by the floor and
|
|
72
|
+
* the remote boundary (remote-boundary.ts), which must find the same
|
|
73
|
+
* subcommand the floor does: an option without a value is one word
|
|
74
|
+
* skipped, whatever it is, so `git --no-pager push` reads as push. */
|
|
75
|
+
export interface GitInvocation {
|
|
76
|
+
readonly sub: string | undefined;
|
|
77
|
+
readonly rest: readonly LooseWord[];
|
|
78
|
+
/** B7: several -C compose, each relative to the one before */
|
|
79
|
+
readonly cds: readonly LooseWord[];
|
|
80
|
+
/** B7: --work-tree X IS the tree a whole-tree command works on */
|
|
81
|
+
readonly workTree: LooseWord | undefined;
|
|
82
|
+
/** B7: --git-dir X is a target in itself (space and = forms both) */
|
|
83
|
+
readonly gitDirs: readonly LooseWord[];
|
|
84
|
+
}
|
|
85
|
+
/** `args` is git's argv without `git`. */
|
|
86
|
+
export declare function gitInvocation(args: readonly LooseWord[]): GitInvocation;
|
|
66
87
|
/**
|
|
67
88
|
* Would the floor refuse this command line? `workspaceRoot` is where the
|
|
68
89
|
* shell runs it.
|
package/dist/floor.js
CHANGED
|
@@ -344,8 +344,8 @@ export function innerLines(argv, home) {
|
|
|
344
344
|
/** How deep command lines inside command lines are followed — `sh -c`,
|
|
345
345
|
* `eval` — and how many are read in all: eval's two readings would
|
|
346
346
|
* otherwise double at every level. */
|
|
347
|
-
const MAX_INNER_DEPTH = 16;
|
|
348
|
-
const MAX_INNER_LINES = 256;
|
|
347
|
+
export const MAX_INNER_DEPTH = 16;
|
|
348
|
+
export const MAX_INNER_LINES = 256;
|
|
349
349
|
const SELECTING = new Set([
|
|
350
350
|
"-name", "-iname", "-path", "-ipath", "-wholename", "-iwholename", "-regex", "-iregex", "-newer", "-mtime", "-mmin", "-atime",
|
|
351
351
|
"-amin", "-ctime", "-cmin", "-size", "-empty", "-user", "-group", "-perm", "-samefile", "-links",
|
|
@@ -355,6 +355,38 @@ const SELECTING = new Set([
|
|
|
355
355
|
const pathShaped = (t) => t === "." || t === "./" || t.startsWith("/") || t.startsWith("~") || t.includes("/") || t.startsWith(":") || (hostDialect().msys && (t.includes("\\") || /^[A-Za-z]:/.test(t)));
|
|
356
356
|
/** B7: pathspec magic from the top of the repository */
|
|
357
357
|
const topMagic = (t) => t.startsWith(":/") || t.startsWith(":(top");
|
|
358
|
+
/** `args` is git's argv without `git`. */
|
|
359
|
+
export function gitInvocation(args) {
|
|
360
|
+
let i = 0;
|
|
361
|
+
const cds = [];
|
|
362
|
+
let workTree;
|
|
363
|
+
const gitDirs = [];
|
|
364
|
+
while (i < args.length && args[i].text.startsWith("-")) {
|
|
365
|
+
const t = args[i].text;
|
|
366
|
+
const eq = /^--(work-tree|git-dir)=(.*)$/.exec(t);
|
|
367
|
+
if (eq !== null) {
|
|
368
|
+
const value = { ...args[i], text: eq[2], unknownAt: -1, tilde: eq[2].startsWith("~") };
|
|
369
|
+
if (eq[1] === "work-tree")
|
|
370
|
+
workTree = value;
|
|
371
|
+
else
|
|
372
|
+
gitDirs.push(value);
|
|
373
|
+
}
|
|
374
|
+
if ((t === "-C" || t === "--work-tree" || t === "--git-dir") && i + 1 < args.length) {
|
|
375
|
+
if (t === "-C")
|
|
376
|
+
cds.push(args[i + 1]);
|
|
377
|
+
else if (t === "--work-tree")
|
|
378
|
+
workTree = args[i + 1];
|
|
379
|
+
else
|
|
380
|
+
gitDirs.push(args[i + 1]);
|
|
381
|
+
i += 2;
|
|
382
|
+
}
|
|
383
|
+
else if (t === "-c" || t === "--namespace" || t === "--exec-path")
|
|
384
|
+
i += 2;
|
|
385
|
+
else
|
|
386
|
+
i += 1;
|
|
387
|
+
}
|
|
388
|
+
return { sub: args[i]?.text, rest: args.slice(i + 1), cds, workTree, gitDirs };
|
|
389
|
+
}
|
|
358
390
|
/** The destructive command's targets, or null when it is not destructive. */
|
|
359
391
|
function destructiveTargets(argv) {
|
|
360
392
|
const name = basename(argv[0]?.text ?? "");
|
|
@@ -398,39 +430,7 @@ function destructiveTargets(argv) {
|
|
|
398
430
|
}
|
|
399
431
|
if (name !== "git")
|
|
400
432
|
return null;
|
|
401
|
-
|
|
402
|
-
// B7: several -C compose, each relative to the one before
|
|
403
|
-
const cds = [];
|
|
404
|
-
// B7: --work-tree X IS the tree a whole-tree command works on; --git-dir
|
|
405
|
-
// X is a target in itself (space and = forms both)
|
|
406
|
-
let workTree;
|
|
407
|
-
const gitDirs = [];
|
|
408
|
-
while (i < args.length && args[i].text.startsWith("-")) {
|
|
409
|
-
const t = args[i].text;
|
|
410
|
-
const eq = /^--(work-tree|git-dir)=(.*)$/.exec(t);
|
|
411
|
-
if (eq !== null) {
|
|
412
|
-
const value = { ...args[i], text: eq[2], unknownAt: -1, tilde: eq[2].startsWith("~") };
|
|
413
|
-
if (eq[1] === "work-tree")
|
|
414
|
-
workTree = value;
|
|
415
|
-
else
|
|
416
|
-
gitDirs.push(value);
|
|
417
|
-
}
|
|
418
|
-
if ((t === "-C" || t === "--work-tree" || t === "--git-dir") && i + 1 < args.length) {
|
|
419
|
-
if (t === "-C")
|
|
420
|
-
cds.push(args[i + 1]);
|
|
421
|
-
else if (t === "--work-tree")
|
|
422
|
-
workTree = args[i + 1];
|
|
423
|
-
else
|
|
424
|
-
gitDirs.push(args[i + 1]);
|
|
425
|
-
i += 2;
|
|
426
|
-
}
|
|
427
|
-
else if (t === "-c" || t === "--namespace" || t === "--exec-path")
|
|
428
|
-
i += 2;
|
|
429
|
-
else
|
|
430
|
-
i += 1;
|
|
431
|
-
}
|
|
432
|
-
const sub = args[i]?.text;
|
|
433
|
-
const rest = args.slice(i + 1);
|
|
433
|
+
const { sub, rest, cds, workTree, gitDirs } = gitInvocation(args);
|
|
434
434
|
const flags = new Set(rest.filter((a) => a.text.startsWith("-")).map((a) => a.text));
|
|
435
435
|
const as = (targets) => ({
|
|
436
436
|
targets: [...targets.map((t) => (t === "repo" && workTree !== undefined ? workTree : t)), ...gitDirs],
|
|
@@ -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 } from "./state.js";
|
|
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,13 +1075,17 @@ 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
|
|
1045
1086
|
// durable log was right there and none of it was shown. Empty for
|
|
1046
1087
|
// a fresh session, so `kiso chat` is byte-identical.
|
|
1047
|
-
showResumeTail(session.log.all);
|
|
1088
|
+
showResumeTail(session.log.all, session.id);
|
|
1048
1089
|
// R2 (owner, 2026-08-27): the resume list is NOT on the opening
|
|
1049
1090
|
// screen. `/resume` is where you go looking for a session; the
|
|
1050
1091
|
// opening's job is to say what THIS one is.
|
|
@@ -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,8 +1116,8 @@ async function chatLoop(first, firstId, input, autoCompact) {
|
|
|
1075
1116
|
void announceUpdate();
|
|
1076
1117
|
}
|
|
1077
1118
|
else {
|
|
1078
|
-
|
|
1079
|
-
showResumeTail(session.log.all);
|
|
1119
|
+
body.notice(`session ${id} (switched — previous: ${prev}, /resume ${prev} returns)\n`);
|
|
1120
|
+
showResumeTail(session.log.all, session.id);
|
|
1080
1121
|
if (currentFaux)
|
|
1081
1122
|
session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
|
|
1082
1123
|
}
|
|
@@ -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
|
|
@@ -1188,10 +1231,12 @@ async function readSecret(prompt) {
|
|
|
1188
1231
|
* there is no viewer and no cell renderer, so the plain text tail stays
|
|
1189
1232
|
* (DC-51: one call, one cell).
|
|
1190
1233
|
*/
|
|
1191
|
-
function showResumeTail(events) {
|
|
1234
|
+
function showResumeTail(events, sessionId) {
|
|
1192
1235
|
const W = process.stdout.columns ?? 80;
|
|
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)
|
|
1193
1238
|
if (dock.active)
|
|
1194
|
-
replayInto(body, events, W);
|
|
1239
|
+
replayInto(body, events, W, tasksForDisplay(tasksFor(sessionId)));
|
|
1195
1240
|
else
|
|
1196
1241
|
bodyLog(resumeTail(events, W).join("\n"));
|
|
1197
1242
|
}
|
|
@@ -1508,6 +1553,10 @@ async function main() {
|
|
|
1508
1553
|
const id = arg ?? newSessionId(sessionsDir());
|
|
1509
1554
|
// v2b: the dock (TTY only) wraps the whole session — the
|
|
1510
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("", "");
|
|
1511
1560
|
dock.enter();
|
|
1512
1561
|
// E area: a resumed session continues the script at its durable
|
|
1513
1562
|
// position — never restarts it (fauxSkip).
|
|
@@ -1532,6 +1581,10 @@ async function main() {
|
|
|
1532
1581
|
// argv[4] is the optional prompt; argv[3] is the session id
|
|
1533
1582
|
// (argv = [node, script, resume, id, prompt?]).
|
|
1534
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("", "");
|
|
1535
1588
|
dock.enter();
|
|
1536
1589
|
const routeLine = arg !== undefined ? enterRouted(arg) : null;
|
|
1537
1590
|
agent = await createCodingAgent(arg, input, modelFlag);
|
|
@@ -1575,7 +1628,7 @@ async function main() {
|
|
|
1575
1628
|
session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
|
|
1576
1629
|
// REL-0152-D5 — the same tail on the explicit-id form. NOT on
|
|
1577
1630
|
// the -p path above: that one's stdout is a machine's input.
|
|
1578
|
-
showResumeTail(session.log.all);
|
|
1631
|
+
showResumeTail(session.log.all, session.id);
|
|
1579
1632
|
await resume(session, prompt, faux, input);
|
|
1580
1633
|
break;
|
|
1581
1634
|
}
|
|
@@ -1786,8 +1839,9 @@ async function main() {
|
|
|
1786
1839
|
// stopped, so a new user could never learn from the tool
|
|
1787
1840
|
// itself how to hand it a key.
|
|
1788
1841
|
const p = palette();
|
|
1789
|
-
console.log(
|
|
1790
|
-
|
|
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` +
|
|
1791
1845
|
" kiso [sessionId] interactive session (default command)\n" +
|
|
1792
1846
|
" kiso chat [sessionId] same as above\n" +
|
|
1793
1847
|
" kiso resume pick a session to continue (TTY picker)\n" +
|
|
@@ -1818,6 +1872,10 @@ async function main() {
|
|
|
1818
1872
|
// A area: no subcommand (or any non-command first argument) IS
|
|
1819
1873
|
// chat — the first argument is the session id.
|
|
1820
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("", "");
|
|
1821
1879
|
dock.enter();
|
|
1822
1880
|
// R-I-p2 (finding R-I-p-2): the bare command passes the SAME
|
|
1823
1881
|
// input source and model flag as chat/resume — the pre-patch
|
|
@@ -1856,8 +1914,11 @@ async function main() {
|
|
|
1856
1914
|
// scroll region, the cursor lands at the input line, no broken
|
|
1857
1915
|
// terminal (kill -9 excepted; `reset` saves it).
|
|
1858
1916
|
dock.exit();
|
|
1917
|
+
// Graphite §8.10: a closed session never leaves a working or
|
|
1918
|
+
// waiting mark in the tab
|
|
1919
|
+
setTitleState("ready");
|
|
1859
1920
|
for (const id of tasksAtExit.unconfirmed)
|
|
1860
|
-
console.error(`${id}: stop unconfirmed —
|
|
1921
|
+
console.error(`${id}: stop unconfirmed — it may still be running`);
|
|
1861
1922
|
if (tasksAtExit.left.length > 0)
|
|
1862
1923
|
console.error(`left running: ${tasksAtExit.left.join(", ")} — reopen this session to hear how ${tasksAtExit.left.length === 1 ? "it ends" : "they end"}`);
|
|
1863
1924
|
// finding #8 (P1): extension dispose runs on the same exit path — a
|