@vincemakes/kiso-code 0.1.20 → 0.1.22

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.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 手感批 B4 (pure move) — the ONE dispatcher: slash commands, exit, and
3
+ * turns. The bodies moved verbatim from chat()'s closure; chat provides
4
+ * the context (the chain, the run state, the prompt arming).
5
+ */
6
+ import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
7
+ import { MODES, getMode, setMode } from "./mode.js";
8
+ import { body, bodyLog } from "./state.js";
9
+ /** The ONE dispatcher — slash commands, exit, and turns. The recovery
10
+ * replay routes through it too — a queued "/last" must never become a
11
+ * user turn (v2c: the rl lives in main, so lines arrive earlier and the
12
+ * queue is the common path). */
13
+ export function dispatch(line, ctx) {
14
+ const trimmed = line.trim();
15
+ if (trimmed === "/help") {
16
+ // Prints the available commands with one-line descriptions.
17
+ // v2a: the command names are the blue identity accent.
18
+ const p = palette();
19
+ const cmd = (name, desc) => `${p.blue}${name}${p.reset} ${desc}`;
20
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
21
+ bodyLog(cmd("/help", "print this list of commands"));
22
+ bodyLog(cmd("/think", "show the last full thinking block"));
23
+ bodyLog(cmd("/last", "show the most recent tool call's input and output"));
24
+ bodyLog(cmd("/status", "show session id, event count, and context estimate"));
25
+ bodyLog(cmd("/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"));
26
+ bodyLog(cmd("/compact", "summarize the older conversation to free context"));
27
+ bodyLog(cmd("exit", "leave the session"));
28
+ ctx.input.prompt();
29
+ });
30
+ return;
31
+ }
32
+ if (trimmed === "/think") {
33
+ // v2b/v2d: print the last COMPLETE thinking block — the body holds
34
+ // it (the ThinkingCell's fold closes at the block's end).
35
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
36
+ const t = body.lastThinking();
37
+ if (t === null) {
38
+ bodyLog("[no thinking yet]");
39
+ }
40
+ else {
41
+ bodyLog(escapeTerminal(t));
42
+ }
43
+ ctx.input.prompt();
44
+ });
45
+ return;
46
+ }
47
+ if (trimmed === "/last") {
48
+ // B 区/v2d: print the FULL input/output of the most recent tool
49
+ // call — the body holds it (the ToolCell's final state). Runs on
50
+ // the chain: after any in-flight turn completes.
51
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
52
+ const tool = body.lastTool();
53
+ if (tool === null) {
54
+ bodyLog("[no tool call yet]");
55
+ }
56
+ else {
57
+ bodyLog(`--- ${tool.name} input ---`);
58
+ bodyLog(escapeTerminal(JSON.stringify(tool.input, null, 2)));
59
+ bodyLog(`--- ${tool.name} output${tool.result.isError ? " (error)" : ""} ---`);
60
+ bodyLog(escapeTerminal(tool.result.content));
61
+ }
62
+ ctx.input.prompt();
63
+ });
64
+ return;
65
+ }
66
+ if (trimmed === "/status") {
67
+ // B 区: session id, durable event count, and the ~ context
68
+ // estimate — all read straight from the live session, nothing
69
+ // stored separately. Runs on the chain after any in-flight turn.
70
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
71
+ const ctxRatio = ctx.estimateCtx();
72
+ const ctxPct = Number.isFinite(ctxRatio) ? `~${Math.round(ctxRatio * 100)}%` : "~?";
73
+ bodyLog(`session ${ctx.session.id}`);
74
+ bodyLog(`${ctx.session.log.all.length} events`);
75
+ bodyLog(`ctx ${ctxPct}`);
76
+ ctx.input.prompt();
77
+ });
78
+ return;
79
+ }
80
+ if (trimmed === "/mode" || trimmed.startsWith("/mode ")) {
81
+ // Modes: /mode alone prints the current tier + the list;
82
+ // /mode <name> switches — the notice cell leaves the audit
83
+ // line in the body, the status bar repaints at once.
84
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
85
+ const m = MODES.find((x) => x === trimmed.slice(5).trim());
86
+ if (trimmed.slice(5).trim() === "") {
87
+ bodyLog(`mode ${getMode()}`);
88
+ bodyLog(`tiers: ${MODES.join(" ")}`);
89
+ }
90
+ else if (m === undefined) {
91
+ bodyLog(`no such mode: ${trimmed.slice(5).trim()}`);
92
+ bodyLog(`tiers: ${MODES.join(" ")}`);
93
+ }
94
+ else {
95
+ setMode(m);
96
+ body.notice(`mode → ${m}`);
97
+ ctx.paintIdle();
98
+ }
99
+ ctx.input.prompt();
100
+ });
101
+ return;
102
+ }
103
+ if (trimmed === "/compact") {
104
+ // /compact (ADR-0044): the older conversation becomes one
105
+ // model summary — an OFF-LOOP call through the session's own
106
+ // adapter, so it must never race a running turn: refused
107
+ // mid-run, with a hint to wait for the turn to end.
108
+ if (ctx.isRunning()) {
109
+ body.notice("[/compact] a turn is running — wait for it to finish");
110
+ return;
111
+ }
112
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
113
+ try {
114
+ const result = await ctx.session.summarize();
115
+ if (result === null) {
116
+ body.notice("[/compact] nothing to compact — fewer than 5 rounds yet");
117
+ }
118
+ else {
119
+ body.notice(`[/compact] saved ~${result.savedTokens.toLocaleString("en-US")} tokens`);
120
+ }
121
+ }
122
+ catch (err) {
123
+ // Honest failure: nothing was persisted, the session
124
+ // is unchanged (ADR-0044 crash semantics).
125
+ body.notice(`[/compact] failed: ${err instanceof Error ? err.message : String(err)}`);
126
+ }
127
+ ctx.input.prompt();
128
+ });
129
+ return;
130
+ }
131
+ if (trimmed === "exit" || trimmed === "") {
132
+ ctx.input.close();
133
+ return;
134
+ }
135
+ // v2c: a turn submitted while another runs waits on the chain — the
136
+ // live count rides the status bar (+N queued).
137
+ ctx.submitTurn(line);
138
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 手感批 B4 (pure move) — the faux-mode glue: the durable script position
3
+ * (fauxSkip), the script sources (env override + the built-in demo), and
4
+ * the exhaustion guard. All bodies moved verbatim from index.ts.
5
+ */
6
+ import type { FauxScript } from "@vincemakes/kiso-evals";
7
+ import { type LineInput } from "./state.js";
8
+ /**
9
+ * E 区: how many faux-script turns a session has already consumed. The faux
10
+ * provider's script counter is per-process, so a FRESH process that resumes
11
+ * a session would restart the script at turn 0 — re-issuing the first
12
+ * scripted call instead of continuing the trajectory. The session log is
13
+ * the durable position: a turn is consumed when it produced a tool_result
14
+ * or an end_turn stop — AND when its tool call is unfinished (started but
15
+ * no result): the recovery completes those turns WITHOUT a provider call
16
+ * (executes the approved call, or fills the human verdict), so the model's
17
+ * next response is the turn AFTER them.
18
+ */
19
+ export declare function fauxSkip(id: string): number;
20
+ /**
21
+ * E 区: KISO_FAUX_SCRIPT=<path> overrides the demo script with a JSON
22
+ * FauxScript file — the kill -9 e2e drives the CLI through an exact
23
+ * multi-tool trajectory. Absent → the built-in demo script.
24
+ */
25
+ export declare function readFauxScript(): FauxScript;
26
+ /**
27
+ * The keyless demo script: tours the tools so `kiso chat` exercises them.
28
+ * FOUR turns: each user turn consumes two model rounds (call → result →
29
+ * summary), so at least two consecutive user turns work in one process
30
+ * (F 组).
31
+ */
32
+ export declare function fauxScript(): FauxScript;
33
+ /** 十: a faux-mode run whose scripted turns are exhausted must NOT print a
34
+ * provider error and exit 0 — the honest outcome is a loud message and a
35
+ * non-zero exit. Thrown as a CONTROLLED exception (never process.exit):
36
+ * the REPL closes, the error propagates through main's finally (so
37
+ * agent.close() runs and no lock is left behind), and main's catch sets
38
+ * the exit code. Only the exhaustion signature (the empty stream after
39
+ * the declared turns) triggers the script-specific message; any other
40
+ * error terminal still exits non-zero. */
41
+ export declare class FauxExhaustionError extends Error {
42
+ constructor(message: string);
43
+ }
44
+ export declare function failOnFauxExhaustion(last: import("@vincemakes/kiso-core").Event | undefined, faux: boolean, input: LineInput | undefined): void;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * 手感批 B4 (pure move) — the faux-mode glue: the durable script position
3
+ * (fauxSkip), the script sources (env override + the built-in demo), and
4
+ * the exhaustion guard. All bodies moved verbatim from index.ts.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { escapeTerminal } from "@vincemakes/kiso-tui";
8
+ import { SessionStore } from "@vincemakes/kiso-runtime";
9
+ import { sessionsDir } from "./state.js";
10
+ /**
11
+ * E 区: how many faux-script turns a session has already consumed. The faux
12
+ * provider's script counter is per-process, so a FRESH process that resumes
13
+ * a session would restart the script at turn 0 — re-issuing the first
14
+ * scripted call instead of continuing the trajectory. The session log is
15
+ * the durable position: a turn is consumed when it produced a tool_result
16
+ * or an end_turn stop — AND when its tool call is unfinished (started but
17
+ * no result): the recovery completes those turns WITHOUT a provider call
18
+ * (executes the approved call, or fills the human verdict), so the model's
19
+ * next response is the turn AFTER them.
20
+ */
21
+ export function fauxSkip(id) {
22
+ const events = new SessionStore(sessionsDir())
23
+ .load(id)
24
+ .map((r) => r.event);
25
+ const results = new Set(events.filter((e) => e.type === "tool_result").map((e) => e.callId));
26
+ return (events.filter((e) => e.type === "tool_result").length +
27
+ events.filter((e) => e.type === "stop" && e.reason === "end_turn").length +
28
+ events.filter((e) => e.type === "tool_call_end" && !results.has(e.callId)).length);
29
+ }
30
+ /**
31
+ * E 区: KISO_FAUX_SCRIPT=<path> overrides the demo script with a JSON
32
+ * FauxScript file — the kill -9 e2e drives the CLI through an exact
33
+ * multi-tool trajectory. Absent → the built-in demo script.
34
+ */
35
+ export function readFauxScript() {
36
+ const path = process.env.KISO_FAUX_SCRIPT;
37
+ if (path === undefined)
38
+ return fauxScript();
39
+ try {
40
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
41
+ if (!Array.isArray(parsed))
42
+ throw new Error("not an array");
43
+ return parsed;
44
+ }
45
+ catch (err) {
46
+ console.error(`[KISO_FAUX_SCRIPT] cannot load ${path}: ${err.message}`);
47
+ process.exit(1);
48
+ }
49
+ }
50
+ /**
51
+ * The keyless demo script: tours the tools so `kiso chat` exercises them.
52
+ * FOUR turns: each user turn consumes two model rounds (call → result →
53
+ * summary), so at least two consecutive user turns work in one process
54
+ * (F 组).
55
+ */
56
+ export function fauxScript() {
57
+ return [
58
+ {
59
+ events: [
60
+ // 自举 P1: a multi-delta thinking block — renders as ONE
61
+ // streaming segment, not one line per token.
62
+ { type: "thinking", text: "Let me think about" },
63
+ { type: "thinking", text: " the workspace" },
64
+ { type: "thinking", text: " before acting." },
65
+ { type: "text_start" },
66
+ { type: "text_delta", text: "I'm the faux model. Let me look at the working directory." },
67
+ { type: "tool_call_end", callId: "c1", name: "list_dir", input: {} },
68
+ { type: "stop", reason: "tool_use" },
69
+ ],
70
+ },
71
+ {
72
+ events: [
73
+ { type: "text_delta", text: "I see the workspace. What would you like me to inspect or change?" },
74
+ { type: "stop", reason: "end_turn" },
75
+ ],
76
+ },
77
+ {
78
+ events: [
79
+ { type: "text_delta", text: "The faux model is still here, with full context." },
80
+ { type: "stop", reason: "end_turn" },
81
+ ],
82
+ },
83
+ {
84
+ events: [
85
+ { type: "text_delta", text: "And this is the end of the scripted tour." },
86
+ { type: "stop", reason: "end_turn" },
87
+ ],
88
+ },
89
+ ];
90
+ }
91
+ /** 十: a faux-mode run whose scripted turns are exhausted must NOT print a
92
+ * provider error and exit 0 — the honest outcome is a loud message and a
93
+ * non-zero exit. Thrown as a CONTROLLED exception (never process.exit):
94
+ * the REPL closes, the error propagates through main's finally (so
95
+ * agent.close() runs and no lock is left behind), and main's catch sets
96
+ * the exit code. Only the exhaustion signature (the empty stream after
97
+ * the declared turns) triggers the script-specific message; any other
98
+ * error terminal still exits non-zero. */
99
+ export class FauxExhaustionError extends Error {
100
+ constructor(message) {
101
+ super(message);
102
+ this.name = "FauxExhaustionError";
103
+ }
104
+ }
105
+ export function failOnFauxExhaustion(last, faux, input) {
106
+ if (!faux)
107
+ return;
108
+ if (last?.type !== "terminal" || last.outcome.kind !== "error")
109
+ return;
110
+ const message = last.outcome.error.message;
111
+ input?.close(); // the REPL must not stay open waiting for a line
112
+ throw new FauxExhaustionError(message.startsWith("provider stream ended without a stop event")
113
+ ? "[faux mode] the scripted demo turns are exhausted — set ANTHROPIC_API_KEY or OPENAI_API_KEY for a real model"
114
+ : `[faux mode] the scripted model failed: ${escapeTerminal(message.slice(0, 200))}`);
115
+ }
package/dist/index.d.ts CHANGED
@@ -14,15 +14,15 @@
14
14
  * Sessions live under $KISO_HOME/sessions (default ~/.kiso/sessions) as
15
15
  * append-only JSONL. Write/edit/shell tools sit behind the approval policy:
16
16
  * the run pauses, asks, and resumes — durably (ADR-0024).
17
+ *
18
+ * 手感批 B4 (pure move): the interactive pieces live beside this file —
19
+ * chat.ts (the REPL + consumeRun), dispatch.ts (the slash dispatcher),
20
+ * resume.ts, trust-ui.ts (the question surface + E3 merges), faux-glue.ts
21
+ * (the scripted-model plumbing), state.ts (the shared process state).
22
+ * index.ts keeps the entry: banner, input sources, the A 区 prompt,
23
+ * makeAgent, and main.
17
24
  */
18
- import { type ProjectArtifacts } from "@vincemakes/kiso-runtime";
19
- /**
20
- * E3 — merge the project's mcp.json and skills into the env BEFORE the
21
- * extension load. A server name in BOTH configs is a LOUD error (a silent
22
- * override would be a supply-chain surprise); a skill name in both merges
23
- * with project-wins and a stderr note. Exported for tests.
24
- */
25
- export declare function applyProjectMerges(artifacts: ProjectArtifacts): void;
25
+ export { applyProjectMerges } from "./trust-ui.js";
26
26
  /**
27
27
  * A 区: read the FIRST present instruction file (AGENTS.md preferred) and
28
28
  * return it as an injected section, or "" when none exists. Truncated at