@vincemakes/kiso-code 0.10.0 → 0.12.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.d.ts +16 -0
- package/dist/chat.js +90 -3
- package/dist/index.js +6 -2
- package/dist/state.d.ts +7 -2
- package/dist/trust-ui.d.ts +4 -2
- package/dist/trust-ui.js +7 -2
- package/package.json +14 -14
package/dist/chat.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* estimates. All bodies moved verbatim from index.ts.
|
|
6
6
|
*/
|
|
7
7
|
import { type RunUsage } from "@vincemakes/kiso-tui";
|
|
8
|
+
import { type SaferOption } from "@vincemakes/kiso-tui";
|
|
8
9
|
import type { AgentSession, Run } from "@vincemakes/kiso-runtime";
|
|
9
10
|
import { type LineInput } from "./state.js";
|
|
10
11
|
/**
|
|
@@ -81,6 +82,21 @@ export declare function usageFromEvent(route: string | undefined, ev: import("@v
|
|
|
81
82
|
* first event. */
|
|
82
83
|
export declare function startStatusSpinner(onTick: (glyph: string) => void): () => void;
|
|
83
84
|
export declare function startShellTail(sessionId: string, callId: string, command: string, startedAt: number): () => void;
|
|
85
|
+
/**
|
|
86
|
+
* Parse the model's answer DEFENSIVELY — anything unexpected is a
|
|
87
|
+
* failure, and a failure degrades honestly.
|
|
88
|
+
*
|
|
89
|
+
* The temptation here is to be clever: salvage a half-parse, coerce a
|
|
90
|
+
* string into a command, accept an object where an array was asked for.
|
|
91
|
+
* All of that produces a list of alternatives the model did not propose,
|
|
92
|
+
* shown to a human deciding whether to run a destructive command. The
|
|
93
|
+
* only honest failure mode is the dim line, so anything that is not
|
|
94
|
+
* exactly the requested shape returns null.
|
|
95
|
+
*
|
|
96
|
+
* A fenced code block is the one accommodation, because models emit it
|
|
97
|
+
* constantly and it changes no content.
|
|
98
|
+
*/
|
|
99
|
+
export declare function parseSaferOptions(text: string): SaferOption[] | null;
|
|
84
100
|
/**
|
|
85
101
|
* Consume a run, answering approval pauses as they arrive. `resumeMode`
|
|
86
102
|
* marks a session.resume() continuation. `faux` picks the status line's
|
package/dist/chat.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync, statSync } from "node:fs";
|
|
8
8
|
import { escapeTerminal, cacheHitPct, idleStatus, palette, renderEvent, renderRecap, runningStatus, toolTarget, STATUS_GLYPHS, } from "@vincemakes/kiso-tui";
|
|
9
|
-
import { editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
9
|
+
import { deletionRiskHint, editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
10
10
|
import { canonicalTargetPath, shellProgressPath } from "@vincemakes/kiso-tools-node";
|
|
11
11
|
import { canonicalizeUsage } from "@vincemakes/kiso-runtime";
|
|
12
12
|
import { dispatch } from "./dispatch.js";
|
|
@@ -178,24 +178,94 @@ function approvalDiff(name, input) {
|
|
|
178
178
|
return null; // never let the diff break the approval
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* TUI2-R3v2 ③ — the safer-options request: its prompt, and its parser.
|
|
183
|
+
*
|
|
184
|
+
* The prompt is deliberately small. It carries the pending call and
|
|
185
|
+
* nothing else — no conversation, no tools, no project context — because
|
|
186
|
+
* everything it does not send is rent the human pays for pressing a
|
|
187
|
+
* button, and because "propose a safer version of THIS command" is a
|
|
188
|
+
* question that needs no history to answer.
|
|
189
|
+
*/
|
|
190
|
+
/** TUI2-R3v2 ③: tools whose NEXT approval is the model's answer to a
|
|
191
|
+
* refusal — the "(amended)" marker's source. Per process, cleared as
|
|
192
|
+
* soon as it is shown: the marker describes ONE call, not a mode. */
|
|
193
|
+
const amendedCalls = new Set();
|
|
194
|
+
const SAFER_SYSTEM_PROMPT = [
|
|
195
|
+
"You propose safer alternatives to a single shell/tool call a human is being asked to approve.",
|
|
196
|
+
"Answer with JSON ONLY: an array of 2-3 objects, each {\"command\": string, \"why\": string}.",
|
|
197
|
+
'"command" is the full replacement call. "why" is ONE short plain-language line saying what it does differently.',
|
|
198
|
+
"Prefer alternatives that avoid irreversible deletion. If you cannot improve on it, answer [].",
|
|
199
|
+
].join(" ");
|
|
200
|
+
/**
|
|
201
|
+
* Parse the model's answer DEFENSIVELY — anything unexpected is a
|
|
202
|
+
* failure, and a failure degrades honestly.
|
|
203
|
+
*
|
|
204
|
+
* The temptation here is to be clever: salvage a half-parse, coerce a
|
|
205
|
+
* string into a command, accept an object where an array was asked for.
|
|
206
|
+
* All of that produces a list of alternatives the model did not propose,
|
|
207
|
+
* shown to a human deciding whether to run a destructive command. The
|
|
208
|
+
* only honest failure mode is the dim line, so anything that is not
|
|
209
|
+
* exactly the requested shape returns null.
|
|
210
|
+
*
|
|
211
|
+
* A fenced code block is the one accommodation, because models emit it
|
|
212
|
+
* constantly and it changes no content.
|
|
213
|
+
*/
|
|
214
|
+
export function parseSaferOptions(text) {
|
|
215
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
216
|
+
const body = (fenced?.[1] ?? text).trim();
|
|
217
|
+
const start = body.indexOf("[");
|
|
218
|
+
const end = body.lastIndexOf("]");
|
|
219
|
+
if (start < 0 || end <= start)
|
|
220
|
+
return null;
|
|
221
|
+
let parsed;
|
|
222
|
+
try {
|
|
223
|
+
parsed = JSON.parse(body.slice(start, end + 1));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
229
|
+
return null;
|
|
230
|
+
const out = [];
|
|
231
|
+
for (const item of parsed.slice(0, 3)) {
|
|
232
|
+
if (typeof item !== "object" || item === null)
|
|
233
|
+
return null;
|
|
234
|
+
const { command, why } = item;
|
|
235
|
+
if (typeof command !== "string" || command.trim() === "")
|
|
236
|
+
return null;
|
|
237
|
+
out.push({ command: command.trim(), why: typeof why === "string" ? why.trim() : "" });
|
|
238
|
+
}
|
|
239
|
+
return out.length === 0 ? null : out;
|
|
240
|
+
}
|
|
181
241
|
/** W21 — the panel view for a permission_requested: the rule line (the
|
|
182
242
|
* why-asked speaker + the §3.5 fix hint), the toolTarget title, the
|
|
183
243
|
* "▸ run paused" status, and the ALWAYS-verbose args. */
|
|
184
|
-
function approvalView(name, ev) {
|
|
244
|
+
function approvalView(name, ev, amended = false) {
|
|
185
245
|
const speaker = ev.speaker ?? "kiso";
|
|
186
246
|
const input = ev.input ?? {};
|
|
187
247
|
// exactOptionalPropertyTypes: the hint is OMITTED when the speaker has
|
|
188
248
|
// no fix (mode:accept-edits, shell in default) — never `hint: undefined`.
|
|
189
249
|
const hint = fixHintFor(speaker, name);
|
|
250
|
+
// TUI2-R3v2 ④: the deletion-risk line, for shell calls whose command
|
|
251
|
+
// matches one of the four irreversible patterns. Local rules, no
|
|
252
|
+
// request, and absent for every other command — which is most of them.
|
|
253
|
+
const risk = name === "shell" ? deletionRiskHint(String(input.command ?? "")) : null;
|
|
190
254
|
return {
|
|
191
255
|
flavor: "approval",
|
|
192
256
|
name,
|
|
193
257
|
title: toolTarget(name, input),
|
|
194
258
|
speaker,
|
|
195
259
|
...(hint !== undefined ? { hint } : {}),
|
|
260
|
+
...(risk !== null ? { riskHint: risk } : {}),
|
|
196
261
|
statusText: "▸ run paused",
|
|
197
262
|
args: approvalArgs(name, input),
|
|
198
263
|
fallbackQuestion: `approve ${escapeTerminal(name)}? (y/n) `,
|
|
264
|
+
// TUI2-R3v2 ③: the v4 frame's "(amended)" marker. It says WHY this
|
|
265
|
+
// call looks different from the one just refused — without it, a
|
|
266
|
+
// second approval for the same tool reads as the product asking
|
|
267
|
+
// twice rather than as the model answering.
|
|
268
|
+
...(amended ? { amended: true } : {}),
|
|
199
269
|
};
|
|
200
270
|
}
|
|
201
271
|
/** The panel's ALWAYS-verbose args: edit_file/write_file → the full ±
|
|
@@ -433,7 +503,21 @@ submitTurn) {
|
|
|
433
503
|
const name = ev.name;
|
|
434
504
|
body.toolApproval(ev.callId, approvalDiff(name, ev.input ?? {}));
|
|
435
505
|
const decisionId = ev.decisionId;
|
|
436
|
-
|
|
506
|
+
// TUI2-R3v2 ③: the on-demand alternatives provider. It is built
|
|
507
|
+
// per approval and captured by the panel; it fires ONLY if the
|
|
508
|
+
// human presses option 3, which is the whole zero-ambient-rent
|
|
509
|
+
// mechanism — no press, no request, nothing in the trace.
|
|
510
|
+
const safer = async () => {
|
|
511
|
+
const answer = await session.sideQuery({
|
|
512
|
+
purpose: "safer-options",
|
|
513
|
+
systemPrompt: SAFER_SYSTEM_PROMPT,
|
|
514
|
+
prompt: `the pending call is: ${name} ${JSON.stringify(ev.input ?? {})}`,
|
|
515
|
+
maxTokens: 500,
|
|
516
|
+
});
|
|
517
|
+
return parseSaferOptions(answer);
|
|
518
|
+
};
|
|
519
|
+
const verdict = await askPanel(input, approvalView(name, ev, amendedCalls.has(name)), { safer });
|
|
520
|
+
amendedCalls.delete(name);
|
|
437
521
|
switch (verdict.action) {
|
|
438
522
|
case "cancel": {
|
|
439
523
|
// round 10: a cancellation is a CONSERVATIVE denial,
|
|
@@ -465,6 +549,9 @@ submitTurn) {
|
|
|
465
549
|
if (verdict.reason.trim() !== "") {
|
|
466
550
|
// No+words — the words become the tool_result; the
|
|
467
551
|
// run continues with the model seeing the denial.
|
|
552
|
+
// TUI2-R3v2 ③: whatever the model proposes next for
|
|
553
|
+
// this tool IS the amended call, and the panel says so.
|
|
554
|
+
amendedCalls.add(name);
|
|
468
555
|
await session.approve(decisionId, false, verdict.reason);
|
|
469
556
|
}
|
|
470
557
|
else {
|
package/dist/index.js
CHANGED
|
@@ -154,8 +154,8 @@ function editorInput(editor) {
|
|
|
154
154
|
},
|
|
155
155
|
// W21: the panel — the editor's own state machine takes the
|
|
156
156
|
// keys; the compositor renders it via the bound state.
|
|
157
|
-
panelAsk(view, onCommit) {
|
|
158
|
-
editor.beginPanel(view, onCommit);
|
|
157
|
+
panelAsk(view, onCommit, opts) {
|
|
158
|
+
editor.beginPanel(view, onCommit, opts);
|
|
159
159
|
},
|
|
160
160
|
panelCancel() {
|
|
161
161
|
editor.cancelPanel();
|
|
@@ -202,6 +202,10 @@ function makeLineInput() {
|
|
|
202
202
|
// "input lives here"; the line-mode path keeps the brick ▌, so
|
|
203
203
|
// pipe bytes do not change)
|
|
204
204
|
dock.bindInput(() => editor.dockState(), "› ");
|
|
205
|
+
// TUI2-R3v2 ②: the click hit-test's wiring — the compositor places
|
|
206
|
+
// the panel's option rows, so the compositor is what the editor asks
|
|
207
|
+
// where they are. Neither side computes the other's geometry.
|
|
208
|
+
editor.bindPanelRows(() => dock.panelOptionRows());
|
|
205
209
|
dock.bindMenu(() => editor.menuState()); // v3 §04: the slash-command menu
|
|
206
210
|
editor.bindAtItems(atFiles); // KC3 §5: the file source — listed per OPEN
|
|
207
211
|
dock.bindAt(() => editor.atState()); // KC3 §4: the picker's band
|
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 AtItem, type Body, type PanelVerdict, type PanelView, type SessionCardView } from "@vincemakes/kiso-tui";
|
|
8
|
+
import { Dock, type AtItem, type Body, type PanelVerdict, type PanelView, type SaferOption, type SessionCardView } from "@vincemakes/kiso-tui";
|
|
9
9
|
import type { KisoExtension, StoreRecord } 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
|
|
@@ -80,7 +80,12 @@ export interface LineInput {
|
|
|
80
80
|
/** W21: open the approval panel — the editor's state machine takes
|
|
81
81
|
* the keys (the digits/y/n/tab/esc/enter routing, the rule input,
|
|
82
82
|
* the tab-amend), the compositor renders the block + the leads. */
|
|
83
|
-
|
|
83
|
+
/** TUI2-R3v2 ③: `opts.safer` is the on-demand alternatives provider —
|
|
84
|
+
* absent for every panel that has no such option (the ask, the pick,
|
|
85
|
+
* the trust gate), so those buttons cannot exist to be pressed. */
|
|
86
|
+
panelAsk(view: PanelView, onCommit: (v: PanelVerdict) => void, opts?: {
|
|
87
|
+
safer?: () => Promise<readonly SaferOption[] | null>;
|
|
88
|
+
}): void;
|
|
84
89
|
/** W21: cancel the panel — the SIGINT pair to panelAsk. */
|
|
85
90
|
panelCancel(): void;
|
|
86
91
|
/** TUI2-R2 ②: open the session picker — the editor takes the keys
|
package/dist/trust-ui.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* chat.ts), the don't-ask-again rule writer, and the uncertain-execution
|
|
6
6
|
* decisions. All bodies moved verbatim from index.ts.
|
|
7
7
|
*/
|
|
8
|
-
import { type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
|
|
8
|
+
import { type PanelVerdict, type PanelView, type SaferOption } from "@vincemakes/kiso-tui";
|
|
9
9
|
import type { AskUI } from "@vincemakes/kiso-ask-ext";
|
|
10
10
|
import { type ProjectArtifacts } from "@vincemakes/kiso-runtime";
|
|
11
11
|
import type { AgentSession } from "@vincemakes/kiso-runtime";
|
|
@@ -34,7 +34,9 @@ import { type LineInput } from "./state.js";
|
|
|
34
34
|
* the dead question.
|
|
35
35
|
*/
|
|
36
36
|
export declare let pendingAsk: (() => void) | null;
|
|
37
|
-
export declare function askPanel(input: LineInput, view: PanelView
|
|
37
|
+
export declare function askPanel(input: LineInput, view: PanelView, opts?: {
|
|
38
|
+
safer?: () => Promise<readonly SaferOption[] | null>;
|
|
39
|
+
}): Promise<PanelVerdict>;
|
|
38
40
|
/**
|
|
39
41
|
* KC3.5 — the AskUI bridge: the panel the ask extension asks through.
|
|
40
42
|
*
|
package/dist/trust-ui.js
CHANGED
|
@@ -38,7 +38,12 @@ import { getMode } from "./mode.js";
|
|
|
38
38
|
* the dead question.
|
|
39
39
|
*/
|
|
40
40
|
export let pendingAsk = null;
|
|
41
|
-
export function askPanel(input, view
|
|
41
|
+
export function askPanel(input, view,
|
|
42
|
+
// TUI2-R3v2 ③: the safer-options provider, when the caller has one.
|
|
43
|
+
// Only the approval site passes it; every other panel (the ask, the
|
|
44
|
+
// pick, the trust gate, the uncertain resolutions) omits it, so their
|
|
45
|
+
// lists cannot grow a button they have no answer for.
|
|
46
|
+
opts) {
|
|
42
47
|
if (!process.stdin.isTTY) {
|
|
43
48
|
console.log(`[non-interactive — no human to ask: ${view.fallbackQuestion}]`);
|
|
44
49
|
return Promise.resolve({ action: "deny", reason: "no human to ask" });
|
|
@@ -63,7 +68,7 @@ export function askPanel(input, view) {
|
|
|
63
68
|
settled = true;
|
|
64
69
|
pendingAsk = null;
|
|
65
70
|
resolve(verdict);
|
|
66
|
-
});
|
|
71
|
+
}, opts);
|
|
67
72
|
}
|
|
68
73
|
else {
|
|
69
74
|
// v2c: a TTY without a dock (rows < 4) — the fallback question
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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,19 +18,19 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-ask-ext": "0.
|
|
22
|
-
"@vincemakes/kiso-core": "0.
|
|
23
|
-
"@vincemakes/kiso-evals": "0.
|
|
24
|
-
"@vincemakes/kiso-mcp-ext": "0.
|
|
25
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
26
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
27
|
-
"@vincemakes/kiso-runtime": "0.
|
|
28
|
-
"@vincemakes/kiso-skills-ext": "0.
|
|
29
|
-
"@vincemakes/kiso-subagent-ext": "0.
|
|
30
|
-
"@vincemakes/kiso-task-ext": "0.
|
|
31
|
-
"@vincemakes/kiso-tools-node": "0.
|
|
32
|
-
"@vincemakes/kiso-tui": "0.
|
|
33
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
21
|
+
"@vincemakes/kiso-ask-ext": "0.12.0",
|
|
22
|
+
"@vincemakes/kiso-core": "0.12.0",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.12.0",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.12.0",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.12.0",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.12.0",
|
|
27
|
+
"@vincemakes/kiso-runtime": "0.12.0",
|
|
28
|
+
"@vincemakes/kiso-skills-ext": "0.12.0",
|
|
29
|
+
"@vincemakes/kiso-subagent-ext": "0.12.0",
|
|
30
|
+
"@vincemakes/kiso-task-ext": "0.12.0",
|
|
31
|
+
"@vincemakes/kiso-tools-node": "0.12.0",
|
|
32
|
+
"@vincemakes/kiso-tui": "0.12.0",
|
|
33
|
+
"@vincemakes/kiso-tui-cells": "0.12.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.1.2",
|