@vincemakes/kiso-code 0.5.0 → 0.7.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.js +59 -19
- package/dist/dispatch.js +5 -2
- package/dist/index.js +10 -3
- package/dist/resume.js +9 -11
- package/dist/state.d.ts +30 -1
- package/dist/state.js +59 -2
- package/dist/trust-ui.d.ts +0 -5
- package/dist/trust-ui.js +6 -33
- package/package.json +13 -13
package/dist/chat.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* estimates. All bodies moved verbatim from index.ts.
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync } from "node:fs";
|
|
8
|
-
import { escapeTerminal,
|
|
8
|
+
import { escapeTerminal, idleStatus, palette, renderEvent, renderRecap, runningStatus, toolTarget, STATUS_GLYPHS, } from "@vincemakes/kiso-tui";
|
|
9
9
|
import { editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
10
10
|
import { canonicalTargetPath } from "@vincemakes/kiso-tools-node";
|
|
11
11
|
import { canonicalizeUsage } from "@vincemakes/kiso-runtime";
|
|
@@ -99,9 +99,9 @@ export function startStatusSpinner(onTick) {
|
|
|
99
99
|
return () => { };
|
|
100
100
|
// v3 §03/§05: the working glyph family ▖▘▝▗, 200ms rotation — the
|
|
101
101
|
// callback repaints the running status line with the new glyph.
|
|
102
|
-
|
|
102
|
+
// KC2 §5: the family itself moved to the tui's status formatters.
|
|
103
103
|
let i = 0;
|
|
104
|
-
const timer = setInterval(() => onTick(
|
|
104
|
+
const timer = setInterval(() => onTick(STATUS_GLYPHS[i++ % STATUS_GLYPHS.length]), 200);
|
|
105
105
|
timer.unref();
|
|
106
106
|
return () => clearInterval(timer);
|
|
107
107
|
}
|
|
@@ -587,6 +587,30 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
587
587
|
currentRun.abort();
|
|
588
588
|
}
|
|
589
589
|
});
|
|
590
|
+
// KC2 §2/§3 — the redirect: "stop, and do THIS instead". No stream
|
|
591
|
+
// injection, no new durable or op state — the run aborts (its terminal
|
|
592
|
+
// is an honest `aborted`) and the buffer's text becomes the next turn.
|
|
593
|
+
// With no run in flight the gesture is simply an Enter, which is what
|
|
594
|
+
// the human means by it: there is nothing to stop.
|
|
595
|
+
input.onRedirect?.((line) => {
|
|
596
|
+
if (currentRun === null)
|
|
597
|
+
return dispatch(line, dispatchCtx);
|
|
598
|
+
console.log("\n[redirecting run]");
|
|
599
|
+
pendingAsk?.();
|
|
600
|
+
currentRun.abort();
|
|
601
|
+
// §3: a correction must run BEFORE the follow-ups queued earlier —
|
|
602
|
+
// it is a correction OF them. The existing slot mechanics compose
|
|
603
|
+
// it: every pending slot leaves through the SAME pop the ↑ key uses
|
|
604
|
+
// (cancelled, so its chain segment skips), then they re-enter
|
|
605
|
+
// BEHIND the correction, in their original order. Ephemeral
|
|
606
|
+
// reordering of ephemeral state; the durable log still just records
|
|
607
|
+
// what ran, in the order it ran.
|
|
608
|
+
const jumped = pendingTurns.map((s) => s.line);
|
|
609
|
+
for (let i = jumped.length; i > 0; i -= 1)
|
|
610
|
+
popQueue();
|
|
611
|
+
for (const text of [line, ...jumped])
|
|
612
|
+
submitTurn(text);
|
|
613
|
+
});
|
|
590
614
|
// round 5 (P1-11): the PERSISTENT line listener is installed BEFORE the
|
|
591
615
|
// startup recovery — a cancelled question's re-emitted "line" needs a
|
|
592
616
|
// listener from the very first instant, or the input is silently lost.
|
|
@@ -617,23 +641,18 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
617
641
|
let runUsage = { in: null, out: null, cache: null, known: false };
|
|
618
642
|
let runGlyph = "▖";
|
|
619
643
|
let runStart = Date.now();
|
|
644
|
+
// KC2 §5: the STATE (the glyph, the run's start, the usage, the dock)
|
|
645
|
+
// stays here; the ROW's text is the tui's status formatter.
|
|
620
646
|
const paintRunning = () => {
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
const ratio = estimateCtxRatio(session);
|
|
624
|
-
const pct = Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
|
|
625
|
-
const out = runUsage.out !== null ? ` ↓ ${kUnit(runUsage.out)} tokens` : "";
|
|
626
|
-
dock.setStatus(`${runGlyph} working ${Math.max(1, Math.round((Date.now() - runStart) / 1000))}s${out} · esc to interrupt · ctx left ~${pct}%`);
|
|
647
|
+
if (dock.active)
|
|
648
|
+
dock.setStatus(runningStatus(runGlyph, runStart, runUsage.out, estimateCtxRatio(session)));
|
|
627
649
|
};
|
|
650
|
+
// W19: under plan the idle row makes the posture unmistakable — the W4
|
|
651
|
+
// parentheses idiom names the read-only constraint. The tier is the
|
|
652
|
+
// CALLER's word (the recovery flow passes the bare mode).
|
|
628
653
|
const paintIdle = () => {
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
const ratio = estimateCtxRatio(session);
|
|
632
|
-
const pct = Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
|
|
633
|
-
// W19: under plan the idle row makes the posture unmistakable —
|
|
634
|
-
// the W4 parentheses idiom names the read-only constraint.
|
|
635
|
-
const tier = getMode() === "plan" ? "plan (read-only)" : getMode();
|
|
636
|
-
dock.setStatus(`▸ ${tier} · /mode to switch · ${agentModel} · ctx left ~${pct}%`);
|
|
654
|
+
if (dock.active)
|
|
655
|
+
dock.setStatus(idleStatus(getMode() === "plan" ? "plan (read-only)" : getMode(), agentModel, estimateCtxRatio(session)));
|
|
637
656
|
};
|
|
638
657
|
const statusCb = (u, ctx) => {
|
|
639
658
|
runUsage = u;
|
|
@@ -643,13 +662,34 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
643
662
|
const slot = { line, cancelled: false };
|
|
644
663
|
pendingTurns.push(slot);
|
|
645
664
|
queued += 1;
|
|
646
|
-
chainRef.current = chainRef.current.then(() => {
|
|
665
|
+
chainRef.current = chainRef.current.then(async () => {
|
|
647
666
|
if (slot.cancelled)
|
|
648
667
|
return; // the pop already dropped it — no double count
|
|
649
668
|
const idx = pendingTurns.indexOf(slot);
|
|
650
669
|
if (idx >= 0)
|
|
651
670
|
pendingTurns.splice(idx, 1); // the chip leaves when the turn STARTS
|
|
652
|
-
|
|
671
|
+
// KC2 §4 — the FRESH-TURN uncertainty gate. The runtime's fresh
|
|
672
|
+
// path checks only the open-run gate before persisting
|
|
673
|
+
// user_input (ResumeBlockedError guards the RESUME derivation
|
|
674
|
+
// alone), and the CLI resolved uncertains at startup recovery
|
|
675
|
+
// only — so a turn queued behind an abort-mid-tool could reach
|
|
676
|
+
// the model before the human said whether the side effect
|
|
677
|
+
// applied. It asks HERE, before the turn starts, with the same
|
|
678
|
+
// recovery UI; a human who declines leaves it uncertain and the
|
|
679
|
+
// turn does not start. The resolution's own model-facing fill
|
|
680
|
+
// also answers the dangling tool_use, so the next request never
|
|
681
|
+
// carries an unanswered call. Composed from existing APIs: zero
|
|
682
|
+
// core lines, zero runtime lines.
|
|
683
|
+
if (session.uncertainExecutions().length > 0)
|
|
684
|
+
await resolveUncertains(session, input, () => cancelled);
|
|
685
|
+
if (session.uncertainExecutions().length === 0)
|
|
686
|
+
return turn(line);
|
|
687
|
+
// The human declined (round 10: a cancelled ask records NOTHING —
|
|
688
|
+
// the execution stays uncertain and durable), so the turn does not
|
|
689
|
+
// start. It is never swallowed in silence: the held text is
|
|
690
|
+
// printed back, so the human can see what is waiting on them.
|
|
691
|
+
queued = Math.max(0, queued - 1);
|
|
692
|
+
body.notice(`[turn held — the interrupted execution is still undecided] ${escapeTerminal(line)}`);
|
|
653
693
|
});
|
|
654
694
|
};
|
|
655
695
|
// W22: the ↑/esc pop — the LAST queued slot leaves the queue
|
package/dist/dispatch.js
CHANGED
|
@@ -28,8 +28,11 @@ export function dispatch(line, ctx) {
|
|
|
28
28
|
bodyLog(cmd("/model", "list model profiles; /model <name|provider/model> switches"));
|
|
29
29
|
bodyLog(cmd("/compact", "summarize the older conversation to free context"));
|
|
30
30
|
// KC1: the composer's keys ride the SAME bodyLog call (it splits
|
|
31
|
-
// on \n) — the help gains a row, the cli source does not
|
|
32
|
-
|
|
31
|
+
// on \n) — the help gains a row, the cli source does not.
|
|
32
|
+
// KC2: the redirect joins the same row for the same reason.
|
|
33
|
+
// KC3: and so does the @ picker — the row is where a gesture is
|
|
34
|
+
// taught, and the row costs nothing.
|
|
35
|
+
bodyLog(`${cmd("exit", "leave the session")}\n${cmd("keys", "enter sends · ctrl+J newline (shift+enter where encoded) · esc stops the run · alt+⏎ stops it and sends this instead · @ files")}`);
|
|
33
36
|
ctx.input.prompt();
|
|
34
37
|
});
|
|
35
38
|
return;
|
package/dist/index.js
CHANGED
|
@@ -26,14 +26,14 @@ import { readFileSync, realpathSync, rmSync } from "node:fs";
|
|
|
26
26
|
import { createInterface } from "node:readline";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
import { join } from "node:path";
|
|
29
|
-
import { Body, Editor, bannerLines, escapeTerminal, palette, renderSessionLine } from "@vincemakes/kiso-tui";
|
|
29
|
+
import { Body, Editor, bannerLines, escapeTerminal, interactivePrompt, palette, renderSessionLine } from "@vincemakes/kiso-tui";
|
|
30
30
|
import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions, SessionStore, } from "@vincemakes/kiso-runtime";
|
|
31
31
|
import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
32
32
|
import { createCodingTools } from "@vincemakes/kiso-tools-node";
|
|
33
33
|
import { MODES, modeExtensions, modeFromEnv, modeSystemPrompt, setMode } from "./mode.js";
|
|
34
34
|
import { builtInLayer } from "./builtin.js";
|
|
35
|
-
import { body, bodyLog, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
|
|
36
|
-
import {
|
|
35
|
+
import { atFiles, body, bodyLog, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
|
|
36
|
+
import { resolveProjectTrust } from "./trust-ui.js";
|
|
37
37
|
import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
|
|
38
38
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
39
39
|
import { autoCompactFromEnv, chat, contextWindowTokens } from "./chat.js";
|
|
@@ -140,6 +140,11 @@ function editorInput(editor) {
|
|
|
140
140
|
onExpand(cb) {
|
|
141
141
|
editor.onExpand(cb);
|
|
142
142
|
},
|
|
143
|
+
// KC2 §2: the redirect gesture — the editor decides WHEN (the
|
|
144
|
+
// same-chunk pair, the precedence gate); chat decides what it MEANS.
|
|
145
|
+
onRedirect(cb) {
|
|
146
|
+
editor.onRedirect(cb);
|
|
147
|
+
},
|
|
143
148
|
question(query, cb) {
|
|
144
149
|
editor.question(query, cb);
|
|
145
150
|
},
|
|
@@ -191,6 +196,8 @@ function makeLineInput() {
|
|
|
191
196
|
// pipe bytes do not change)
|
|
192
197
|
dock.bindInput(() => editor.dockState(), "› ");
|
|
193
198
|
dock.bindMenu(() => editor.menuState()); // v3 §04: the slash-command menu
|
|
199
|
+
editor.bindAtItems(atFiles); // KC3 §5: the file source — listed per OPEN
|
|
200
|
+
dock.bindAt(() => editor.atState()); // KC3 §4: the picker's band
|
|
194
201
|
dock.bindApproval(() => editor.panelState()); // W21: the panel's bound state
|
|
195
202
|
return editorInput(editor);
|
|
196
203
|
}
|
package/dist/resume.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The ergonomics batch B4 (pure move) — resume: the RECOVERY flow (Area 2/7). The body
|
|
3
3
|
* moved verbatim from index.ts.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { idleStatus, runningStatus } from "@vincemakes/kiso-tui";
|
|
6
6
|
import { getMode } from "./mode.js";
|
|
7
7
|
import { agentModel, dock } from "./state.js";
|
|
8
8
|
import { pendingAsk, resolveUncertains } from "./trust-ui.js";
|
|
@@ -23,20 +23,18 @@ export async function resume(session, prompt, faux, input) {
|
|
|
23
23
|
let runUsage = { in: null, out: null, cache: null, known: false };
|
|
24
24
|
let runGlyph = "▖";
|
|
25
25
|
let runStart = Date.now();
|
|
26
|
+
// KC2 §5: the rows the REPL and this flow used to build separately are
|
|
27
|
+
// ONE formatter now — the running row was duplicated verbatim.
|
|
26
28
|
const statusCb = (u, ctx) => {
|
|
27
29
|
runUsage = u;
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
const pct = Number.isFinite(ctx) ? Math.round((1 - ctx) * 100) : null;
|
|
31
|
-
const out = runUsage.out !== null ? ` ↓ ${kUnit(runUsage.out)} tokens` : "";
|
|
32
|
-
dock.setStatus(`${runGlyph} working ${Math.max(1, Math.round((Date.now() - runStart) / 1000))}s${out} · esc to interrupt · ctx left ~${pct}%`);
|
|
30
|
+
if (dock.active)
|
|
31
|
+
dock.setStatus(runningStatus(runGlyph, runStart, u.out, ctx));
|
|
33
32
|
};
|
|
33
|
+
// the recovery flow prints the BARE mode (chat spells plan's posture) —
|
|
34
|
+
// the extraction keeps that difference, it was not asked to settle it.
|
|
34
35
|
const paintIdle = () => {
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
const ratio = estimateCtxRatio(session);
|
|
38
|
-
const pct = Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
|
|
39
|
-
dock.setStatus(`▸ ${getMode()} · /mode to switch · ${agentModel} · ctx left ~${pct}%`);
|
|
36
|
+
if (dock.active)
|
|
37
|
+
dock.setStatus(idleStatus(getMode(), agentModel, estimateCtxRatio(session)));
|
|
40
38
|
};
|
|
41
39
|
const withRun = async (run) => {
|
|
42
40
|
currentRun = run;
|
package/dist/state.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* creates the mutable ones (setBody / setAgentModel / setExtensionLists);
|
|
6
6
|
* the moved modules read and mutate at call time.
|
|
7
7
|
*/
|
|
8
|
-
import { Dock, type Body, type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
|
|
8
|
+
import { Dock, type AtItem, type Body, type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
|
|
9
9
|
import type { KisoExtension } from "@vincemakes/kiso-runtime";
|
|
10
10
|
/** finding #11: KISO_HOME is the ONE root — every default path derives from
|
|
11
11
|
* it (sessions, trust, extensions, mcp config, skills). The dedicated
|
|
@@ -15,6 +15,30 @@ 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
|
+
* KC3 §5 — the @ picker's file source. Computed PER OPEN: no index, no
|
|
20
|
+
* daemon, no watcher, nothing to invalidate and nothing to go stale.
|
|
21
|
+
* (The editor snapshots the result for the life of one open, so this
|
|
22
|
+
* runs once per `@`, not once per keystroke.)
|
|
23
|
+
*
|
|
24
|
+
* In a git repo, git IS the answer. `ls-files -c -o --exclude-standard`
|
|
25
|
+
* is the tracked files AND the untracked ones that are not ignored, in
|
|
26
|
+
* ONE process: the same set the user's own tooling calls "the project",
|
|
27
|
+
* with every .gitignore in the tree already honoured — and honoured by
|
|
28
|
+
* git rather than by a reimplementation of git's rules. stderr is
|
|
29
|
+
* discarded because "not a git repository" must never land in a live
|
|
30
|
+
* TUI frame.
|
|
31
|
+
*
|
|
32
|
+
* Outside a repo — or with no git on PATH — the bounded walk stands
|
|
33
|
+
* in: it prunes AT_SKIP before descending and stops at AT_CAP + 1
|
|
34
|
+
* entries, the extra entry being what lets the panel's counter SAY it
|
|
35
|
+
* truncated instead of showing the first two thousand files as though
|
|
36
|
+
* they were all of them.
|
|
37
|
+
*
|
|
38
|
+
* Paths are forward-slashed on both branches (git's own format), so
|
|
39
|
+
* the fuzzy filter sees one alphabet whichever branch ran.
|
|
40
|
+
*/
|
|
41
|
+
export declare function atFiles(): readonly AtItem[];
|
|
18
42
|
/**
|
|
19
43
|
* v2c — the interactive input source. TTYs use the raw-mode Editor (the
|
|
20
44
|
* self-drawn input row — width-aware, the CJK-drift root cause retired,
|
|
@@ -30,6 +54,11 @@ export interface LineInput {
|
|
|
30
54
|
/** W15: the expand key (ctrl+r) — the chain-level action, never the
|
|
31
55
|
* editor's own interpretation. */
|
|
32
56
|
onExpand(cb: () => void): void;
|
|
57
|
+
/** KC2 §2: the redirect gesture (Alt+Enter / Ctrl+Enter) — the
|
|
58
|
+
* buffer's text arrives as a line at the same instant the run is told
|
|
59
|
+
* to stop. OPTIONAL: the pipe path has no raw keys and never wires
|
|
60
|
+
* it, so readline stays exactly as it was. */
|
|
61
|
+
onRedirect?(cb: (line: string) => void): void;
|
|
33
62
|
question(query: string, cb: (answer: string) => void): void;
|
|
34
63
|
cancelQuestion(): void;
|
|
35
64
|
/** W21: open the approval panel — the editor's state machine takes
|
package/dist/state.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* creates the mutable ones (setBody / setAgentModel / setExtensionLists);
|
|
6
6
|
* the moved modules read and mutate at call time.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
import { dirname, join } from "node:path";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { Dock } from "@vincemakes/kiso-tui";
|
|
13
|
+
import { AT_CAP, AT_SKIP, Dock } from "@vincemakes/kiso-tui";
|
|
13
14
|
/** finding #11: KISO_HOME is the ONE root — every default path derives from
|
|
14
15
|
* it (sessions, trust, extensions, mcp config, skills). The dedicated
|
|
15
16
|
* env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
|
|
@@ -24,6 +25,62 @@ export function sessionsDir() {
|
|
|
24
25
|
export function extensionsDir() {
|
|
25
26
|
return process.env.KISO_EXTENSIONS_DIR ?? join(kisoHome(), "extensions");
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* KC3 §5 — the @ picker's file source. Computed PER OPEN: no index, no
|
|
30
|
+
* daemon, no watcher, nothing to invalidate and nothing to go stale.
|
|
31
|
+
* (The editor snapshots the result for the life of one open, so this
|
|
32
|
+
* runs once per `@`, not once per keystroke.)
|
|
33
|
+
*
|
|
34
|
+
* In a git repo, git IS the answer. `ls-files -c -o --exclude-standard`
|
|
35
|
+
* is the tracked files AND the untracked ones that are not ignored, in
|
|
36
|
+
* ONE process: the same set the user's own tooling calls "the project",
|
|
37
|
+
* with every .gitignore in the tree already honoured — and honoured by
|
|
38
|
+
* git rather than by a reimplementation of git's rules. stderr is
|
|
39
|
+
* discarded because "not a git repository" must never land in a live
|
|
40
|
+
* TUI frame.
|
|
41
|
+
*
|
|
42
|
+
* Outside a repo — or with no git on PATH — the bounded walk stands
|
|
43
|
+
* in: it prunes AT_SKIP before descending and stops at AT_CAP + 1
|
|
44
|
+
* entries, the extra entry being what lets the panel's counter SAY it
|
|
45
|
+
* truncated instead of showing the first two thousand files as though
|
|
46
|
+
* they were all of them.
|
|
47
|
+
*
|
|
48
|
+
* Paths are forward-slashed on both branches (git's own format), so
|
|
49
|
+
* the fuzzy filter sees one alphabet whichever branch ran.
|
|
50
|
+
*/
|
|
51
|
+
export function atFiles() {
|
|
52
|
+
let paths;
|
|
53
|
+
try {
|
|
54
|
+
paths = execFileSync("git", ["ls-files", "-c", "-o", "--exclude-standard"], { cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 1 << 24 }).split("\n").filter((p) => p !== "");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
paths = atWalk(process.cwd(), "", []);
|
|
58
|
+
}
|
|
59
|
+
return paths.slice(0, AT_CAP + 1).map((path) => ({ path }));
|
|
60
|
+
}
|
|
61
|
+
/** The fallback walk. `prefix` carries the repo-relative directory so
|
|
62
|
+
* the result needs no path arithmetic afterwards. An unreadable
|
|
63
|
+
* directory contributes nothing and ends nothing — the recursion's own
|
|
64
|
+
* try catches it at that level, so one locked subtree cannot stop a
|
|
65
|
+
* file picker from opening. */
|
|
66
|
+
function atWalk(dir, prefix, out) {
|
|
67
|
+
try {
|
|
68
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
69
|
+
if (out.length > AT_CAP)
|
|
70
|
+
break;
|
|
71
|
+
if (AT_SKIP.has(e.name))
|
|
72
|
+
continue;
|
|
73
|
+
if (e.isDirectory())
|
|
74
|
+
atWalk(`${dir}/${e.name}`, `${prefix}${e.name}/`, out);
|
|
75
|
+
else if (e.isFile())
|
|
76
|
+
out.push(`${prefix}${e.name}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// unreadable — skipped
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
27
84
|
/** v2b: the bottom-anchored UI — docked only on a color TTY; pipes and
|
|
28
85
|
* NO_COLOR stay the v2a line mode byte-for-byte. Created at load, like
|
|
29
86
|
* the pre-split module-scope const. */
|
package/dist/trust-ui.d.ts
CHANGED
|
@@ -9,11 +9,6 @@ import { type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
|
|
|
9
9
|
import { type ProjectArtifacts } from "@vincemakes/kiso-runtime";
|
|
10
10
|
import type { AgentSession } from "@vincemakes/kiso-runtime";
|
|
11
11
|
import { type LineInput } from "./state.js";
|
|
12
|
-
/** v2a: the interactive prompt — blue, the identity accent. readline owns
|
|
13
|
-
* the echo of what the user types; we own the prompt's color. (v2c: the
|
|
14
|
-
* readline prompt keeps "you> " — the brick ▌ is the dock's row only;
|
|
15
|
-
* pipe bytes must not change.) */
|
|
16
|
-
export declare function interactivePrompt(): string;
|
|
17
12
|
/**
|
|
18
13
|
* W21 — ask the human with the approval panel: the bounded block that
|
|
19
14
|
* replaces the running tool's live window while the approval is pending.
|
package/dist/trust-ui.js
CHANGED
|
@@ -9,19 +9,11 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, symlinkS
|
|
|
9
9
|
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
|
-
import {
|
|
12
|
+
import { projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView } from "@vincemakes/kiso-tui";
|
|
13
13
|
import { projectArtifacts, recordTrust, trustFor } from "@vincemakes/kiso-runtime";
|
|
14
14
|
import { bodyLog, currentAgentExtensions, dock, extensionsDir, kisoHome, mergedTempPaths } from "./state.js";
|
|
15
15
|
import { loadUserConfig, resolveProjectTrustPolicy } from "./config.js";
|
|
16
16
|
import { getMode } from "./mode.js";
|
|
17
|
-
/** v2a: the interactive prompt — blue, the identity accent. readline owns
|
|
18
|
-
* the echo of what the user types; we own the prompt's color. (v2c: the
|
|
19
|
-
* readline prompt keeps "you> " — the brick ▌ is the dock's row only;
|
|
20
|
-
* pipe bytes must not change.) */
|
|
21
|
-
export function interactivePrompt() {
|
|
22
|
-
const p = palette();
|
|
23
|
-
return `${p.bold}you> ${p.reset}`;
|
|
24
|
-
}
|
|
25
17
|
/**
|
|
26
18
|
* W21 — ask the human with the approval panel: the bounded block that
|
|
27
19
|
* replaces the running tool's live window while the approval is pending.
|
|
@@ -192,7 +184,7 @@ export async function resolveProjectTrust(input) {
|
|
|
192
184
|
// First discovery — list every artifact (file name + digest short
|
|
193
185
|
// prefix) and ask the human ONCE.
|
|
194
186
|
if (!process.stdin.isTTY) {
|
|
195
|
-
console.error(
|
|
187
|
+
console.error(projectUntrustedNote(artifacts.files.length, artifacts.root));
|
|
196
188
|
return null;
|
|
197
189
|
}
|
|
198
190
|
// v2c: the shared input (the editor on a TTY) reads the answer.
|
|
@@ -201,19 +193,9 @@ export async function resolveProjectTrust(input) {
|
|
|
201
193
|
// the bodyLog below records, verbatim — the listing still lands in
|
|
202
194
|
// the scrollback; the panel is a bounded block, the record is not).
|
|
203
195
|
bodyLog(`[project .kiso] ${artifacts.root}`);
|
|
204
|
-
for (const
|
|
205
|
-
bodyLog(
|
|
206
|
-
|
|
207
|
-
const verdict = await askPanel(input, {
|
|
208
|
-
flavor: "simple",
|
|
209
|
-
name: "project trust",
|
|
210
|
-
title: artifacts.root,
|
|
211
|
-
speaker: "kiso",
|
|
212
|
-
statusText: "▸ project trust",
|
|
213
|
-
args: { kind: "text", lines: artifacts.files.map((f) => `${f.path} (${f.digest.slice(0, 6)})`) },
|
|
214
|
-
ruleOverride: "trust this project's .kiso?",
|
|
215
|
-
fallbackQuestion: `trust this project's .kiso? (y/n) `,
|
|
216
|
-
});
|
|
196
|
+
for (const row of projectTrustRows(artifacts.files, " "))
|
|
197
|
+
bodyLog(row);
|
|
198
|
+
const verdict = await askPanel(input, projectTrustView(artifacts.root, artifacts.files));
|
|
217
199
|
// A cancel is a "no" HERE — refused is sticky, the project does not
|
|
218
200
|
// load (re-evaluate by deleting the trust line or changing a file).
|
|
219
201
|
// The non-TTY branch above returned WITHOUT a record so an interactive
|
|
@@ -317,16 +299,7 @@ function readdirSyncSafe(dir) {
|
|
|
317
299
|
* the body is the single stdout writer, never a stray console.log. */
|
|
318
300
|
export async function resolveUncertains(session, input, isCancelled) {
|
|
319
301
|
for (const uncertain of session.uncertainExecutions()) {
|
|
320
|
-
const verdict = await askPanel(input,
|
|
321
|
-
flavor: "simple",
|
|
322
|
-
name: "uncertain execution",
|
|
323
|
-
title: `${uncertain.name} (${uncertain.executionId})`,
|
|
324
|
-
speaker: "kiso",
|
|
325
|
-
statusText: "▸ uncertain execution",
|
|
326
|
-
args: { kind: "text", lines: [uncertain.executionId] },
|
|
327
|
-
ruleOverride: "did the interrupted execution apply? — 1 rerun · 3 abandon",
|
|
328
|
-
fallbackQuestion: `⚠ interrupted execution: ${escapeTerminal(uncertain.name)} (${uncertain.executionId}) — did it apply? (y)es / (n)o `,
|
|
329
|
-
});
|
|
302
|
+
const verdict = await askPanel(input, uncertainView(uncertain.name, uncertain.executionId));
|
|
330
303
|
if (isCancelled() || verdict.action === "cancel") {
|
|
331
304
|
// round 10: a cancellation NEVER records a verdict — the execution
|
|
332
305
|
// stays uncertain and durable; no rerun/abandoned is fabricated.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,18 +18,18 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-core": "0.
|
|
22
|
-
"@vincemakes/kiso-evals": "0.
|
|
23
|
-
"@vincemakes/kiso-mcp-ext": "0.
|
|
24
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
25
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
26
|
-
"@vincemakes/kiso-runtime": "0.
|
|
27
|
-
"@vincemakes/kiso-skills-ext": "0.
|
|
28
|
-
"@vincemakes/kiso-subagent-ext": "0.
|
|
29
|
-
"@vincemakes/kiso-task-ext": "0.
|
|
30
|
-
"@vincemakes/kiso-tools-node": "0.
|
|
31
|
-
"@vincemakes/kiso-tui": "0.
|
|
32
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
21
|
+
"@vincemakes/kiso-core": "0.7.0",
|
|
22
|
+
"@vincemakes/kiso-evals": "0.7.0",
|
|
23
|
+
"@vincemakes/kiso-mcp-ext": "0.7.0",
|
|
24
|
+
"@vincemakes/kiso-provider-anthropic": "0.7.0",
|
|
25
|
+
"@vincemakes/kiso-provider-openai": "0.7.0",
|
|
26
|
+
"@vincemakes/kiso-runtime": "0.7.0",
|
|
27
|
+
"@vincemakes/kiso-skills-ext": "0.7.0",
|
|
28
|
+
"@vincemakes/kiso-subagent-ext": "0.7.0",
|
|
29
|
+
"@vincemakes/kiso-task-ext": "0.7.0",
|
|
30
|
+
"@vincemakes/kiso-tools-node": "0.7.0",
|
|
31
|
+
"@vincemakes/kiso-tui": "0.7.0",
|
|
32
|
+
"@vincemakes/kiso-tui-cells": "0.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^26.1.2",
|