@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.
- package/dist/chat.d.ts +49 -0
- package/dist/chat.js +483 -0
- package/dist/dispatch.d.ts +30 -0
- package/dist/dispatch.js +138 -0
- package/dist/faux-glue.d.ts +44 -0
- package/dist/faux-glue.js +115 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.js +31 -985
- package/dist/resume.d.ts +14 -0
- package/dist/resume.js +104 -0
- package/dist/state.d.ts +71 -0
- package/dist/state.js +78 -0
- package/dist/trust-ui.d.ts +50 -0
- package/dist/trust-ui.js +218 -0
- package/package.json +8 -8
package/dist/dispatch.js
ADDED
|
@@ -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
|
-
|
|
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
|