@vincemakes/kiso-code 0.12.0 → 0.13.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 +48 -1
- package/dist/chat.js +124 -16
- package/dist/state.d.ts +5 -3
- package/dist/trust-ui.d.ts +2 -2
- package/package.json +14 -14
package/dist/chat.d.ts
CHANGED
|
@@ -5,7 +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
|
+
import { type SaferFailure, type SaferOption } from "@vincemakes/kiso-tui";
|
|
9
9
|
import type { AgentSession, Run } from "@vincemakes/kiso-runtime";
|
|
10
10
|
import { type LineInput } from "./state.js";
|
|
11
11
|
/**
|
|
@@ -82,6 +82,45 @@ export declare function usageFromEvent(route: string | undefined, ev: import("@v
|
|
|
82
82
|
* first event. */
|
|
83
83
|
export declare function startStatusSpinner(onTick: (glyph: string) => void): () => void;
|
|
84
84
|
export declare function startShellTail(sessionId: string, callId: string, command: string, startedAt: number): () => void;
|
|
85
|
+
/**
|
|
86
|
+
* R3v2-F1: the format contract, stated FIRMLY. The first cut asked for
|
|
87
|
+
* "JSON ONLY" and left it there, which a verbose model reads as a
|
|
88
|
+
* preference — it wrote three sentences of preamble, opened a fence, and
|
|
89
|
+
* the cap ended the reply mid-string. Forbidding prose, naming the exact
|
|
90
|
+
* schema, and giving the nothing-is-safer case its own literal answer
|
|
91
|
+
* are all the same instruction: there is one thing to emit and no room
|
|
92
|
+
* to be helpful in the margins.
|
|
93
|
+
*
|
|
94
|
+
* The schema is an ENVELOPE rather than a bare array because a single
|
|
95
|
+
* top-level object leaves the model nowhere to put a preamble.
|
|
96
|
+
*/
|
|
97
|
+
export declare const SAFER_SYSTEM_PROMPT: string;
|
|
98
|
+
/**
|
|
99
|
+
* R3v2-F1: the side query's output ceiling — raised from 500, which was
|
|
100
|
+
* the cap the live failures hit EXACTLY.
|
|
101
|
+
*
|
|
102
|
+
* The JSON-only reply the prompt now asks for is about 200 tokens for
|
|
103
|
+
* three alternatives, so this ceiling is a runaway guard and not a
|
|
104
|
+
* budget the answer is expected to approach: it exists so a model that
|
|
105
|
+
* ignores the contract and writes an essay still stops, not so the
|
|
106
|
+
* answer has room. Output is billed only when generated, and the query
|
|
107
|
+
* fires only on a press, so the raise costs nothing on the path that
|
|
108
|
+
* works and removes the one that could not.
|
|
109
|
+
*/
|
|
110
|
+
export declare const SAFER_MAX_TOKENS = 1500;
|
|
111
|
+
/**
|
|
112
|
+
* R3v2-F1: WHY the ask failed, when the reply's own text can prove it.
|
|
113
|
+
*
|
|
114
|
+
* This side reports the cause and never the copy — the sentences live in
|
|
115
|
+
* the panel package, next to each other, so there is one place where the
|
|
116
|
+
* words are chosen and one place they can drift from.
|
|
117
|
+
*
|
|
118
|
+
* "Cut short" is a DIAGNOSIS, so it is only claimed when the text shows
|
|
119
|
+
* it: a reply that closed its JSON and then failed our SHAPE returns
|
|
120
|
+
* null and gets the unqualified line, because telling that human their
|
|
121
|
+
* reply was truncated would be a confident wrong answer.
|
|
122
|
+
*/
|
|
123
|
+
export declare function saferFailure(text: string): SaferFailure | null;
|
|
85
124
|
/**
|
|
86
125
|
* Parse the model's answer DEFENSIVELY — anything unexpected is a
|
|
87
126
|
* failure, and a failure degrades honestly.
|
|
@@ -95,6 +134,14 @@ export declare function startShellTail(sessionId: string, callId: string, comman
|
|
|
95
134
|
*
|
|
96
135
|
* A fenced code block is the one accommodation, because models emit it
|
|
97
136
|
* constantly and it changes no content.
|
|
137
|
+
*
|
|
138
|
+
* R3v2-F1 widens that accommodation and NOTHING else. Unwrapping the
|
|
139
|
+
* named `alternatives` envelope, and reading `reason` as the spelling of
|
|
140
|
+
* `why` the prompt now asks for, are transport details: the entries that
|
|
141
|
+
* come out are verbatim the entries the model put in. That is the line
|
|
142
|
+
* between an accommodation and the salvage this parser refuses — a
|
|
143
|
+
* salvage changes WHICH alternatives are shown, and every rule that does
|
|
144
|
+
* that is still here. One bad entry still poisons the batch.
|
|
98
145
|
*/
|
|
99
146
|
export declare function parseSaferOptions(text: string): SaferOption[] | null;
|
|
100
147
|
/**
|
package/dist/chat.js
CHANGED
|
@@ -191,12 +191,106 @@ function approvalDiff(name, input) {
|
|
|
191
191
|
* refusal — the "(amended)" marker's source. Per process, cleared as
|
|
192
192
|
* soon as it is shown: the marker describes ONE call, not a mode. */
|
|
193
193
|
const amendedCalls = new Set();
|
|
194
|
-
|
|
194
|
+
/**
|
|
195
|
+
* R3v2-F1: the format contract, stated FIRMLY. The first cut asked for
|
|
196
|
+
* "JSON ONLY" and left it there, which a verbose model reads as a
|
|
197
|
+
* preference — it wrote three sentences of preamble, opened a fence, and
|
|
198
|
+
* the cap ended the reply mid-string. Forbidding prose, naming the exact
|
|
199
|
+
* schema, and giving the nothing-is-safer case its own literal answer
|
|
200
|
+
* are all the same instruction: there is one thing to emit and no room
|
|
201
|
+
* to be helpful in the margins.
|
|
202
|
+
*
|
|
203
|
+
* The schema is an ENVELOPE rather than a bare array because a single
|
|
204
|
+
* top-level object leaves the model nowhere to put a preamble.
|
|
205
|
+
*/
|
|
206
|
+
export const SAFER_SYSTEM_PROMPT = [
|
|
195
207
|
"You propose safer alternatives to a single shell/tool call a human is being asked to approve.",
|
|
196
|
-
"
|
|
197
|
-
'
|
|
198
|
-
"
|
|
208
|
+
"Reply with JSON ONLY — no prose, no preamble, no code fence, nothing before or after the JSON.",
|
|
209
|
+
'The exact schema is {"alternatives":[{"command":"...","reason":"..."}]}, with 2-3 entries.',
|
|
210
|
+
'"command" is the full replacement call. "reason" is ONE line of plain language saying what it does differently.',
|
|
211
|
+
'Prefer alternatives that avoid irreversible deletion. If you cannot improve on it, reply {"alternatives":[]}.',
|
|
199
212
|
].join(" ");
|
|
213
|
+
/**
|
|
214
|
+
* R3v2-F1: the side query's output ceiling — raised from 500, which was
|
|
215
|
+
* the cap the live failures hit EXACTLY.
|
|
216
|
+
*
|
|
217
|
+
* The JSON-only reply the prompt now asks for is about 200 tokens for
|
|
218
|
+
* three alternatives, so this ceiling is a runaway guard and not a
|
|
219
|
+
* budget the answer is expected to approach: it exists so a model that
|
|
220
|
+
* ignores the contract and writes an essay still stops, not so the
|
|
221
|
+
* answer has room. Output is billed only when generated, and the query
|
|
222
|
+
* fires only on a press, so the raise costs nothing on the path that
|
|
223
|
+
* works and removes the one that could not.
|
|
224
|
+
*/
|
|
225
|
+
export const SAFER_MAX_TOKENS = 1500;
|
|
226
|
+
/**
|
|
227
|
+
* R3v2-F1: WHY the ask failed, when the reply's own text can prove it.
|
|
228
|
+
*
|
|
229
|
+
* This side reports the cause and never the copy — the sentences live in
|
|
230
|
+
* the panel package, next to each other, so there is one place where the
|
|
231
|
+
* words are chosen and one place they can drift from.
|
|
232
|
+
*
|
|
233
|
+
* "Cut short" is a DIAGNOSIS, so it is only claimed when the text shows
|
|
234
|
+
* it: a reply that closed its JSON and then failed our SHAPE returns
|
|
235
|
+
* null and gets the unqualified line, because telling that human their
|
|
236
|
+
* reply was truncated would be a confident wrong answer.
|
|
237
|
+
*/
|
|
238
|
+
export function saferFailure(text) {
|
|
239
|
+
return jsonBody(text) === "truncated" ? { reason: "truncated" } : null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* R3v2-F1: find the reply's JSON body by BALANCING brackets rather than
|
|
243
|
+
* by first-and-last.
|
|
244
|
+
*
|
|
245
|
+
* `indexOf("[")` / `lastIndexOf("]")` had two failure modes a verbose
|
|
246
|
+
* model hits constantly: a bracket in the trailing prose moved the end
|
|
247
|
+
* past the array, and a reply the cap cut in half had no end at all.
|
|
248
|
+
* Both returned null, and null could not say which — which is why the
|
|
249
|
+
* degradation line could not either.
|
|
250
|
+
*
|
|
251
|
+
* Returns the balanced slice, `"truncated"` when a value opens and the
|
|
252
|
+
* text ends before it closes, or null when there is no JSON value at
|
|
253
|
+
* all.
|
|
254
|
+
*/
|
|
255
|
+
function jsonBody(text) {
|
|
256
|
+
// a CLOSED fence is content-preserving to strip. An OPEN one means
|
|
257
|
+
// the reply ended inside the block — drop the opener and let the scan
|
|
258
|
+
// below reach the same verdict from the content.
|
|
259
|
+
const closed = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
260
|
+
const body = closed?.[1] ?? text.replace(/^[\s\S]*?```(?:json)?[ \t]*\r?\n/, "");
|
|
261
|
+
const start = body.search(/[[{]/);
|
|
262
|
+
if (start < 0)
|
|
263
|
+
return null;
|
|
264
|
+
let depth = 0;
|
|
265
|
+
let inString = false;
|
|
266
|
+
let escaped = false;
|
|
267
|
+
for (let i = start; i < body.length; i += 1) {
|
|
268
|
+
const c = body[i];
|
|
269
|
+
if (escaped) {
|
|
270
|
+
escaped = false;
|
|
271
|
+
}
|
|
272
|
+
else if (inString) {
|
|
273
|
+
if (c === "\\")
|
|
274
|
+
escaped = true;
|
|
275
|
+
else if (c === '"')
|
|
276
|
+
inString = false;
|
|
277
|
+
}
|
|
278
|
+
else if (c === '"') {
|
|
279
|
+
inString = true;
|
|
280
|
+
}
|
|
281
|
+
else if (c === "[" || c === "{") {
|
|
282
|
+
depth += 1;
|
|
283
|
+
}
|
|
284
|
+
else if (c === "]" || c === "}") {
|
|
285
|
+
depth -= 1;
|
|
286
|
+
if (depth === 0)
|
|
287
|
+
return body.slice(start, i + 1);
|
|
288
|
+
if (depth < 0)
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return "truncated";
|
|
293
|
+
}
|
|
200
294
|
/**
|
|
201
295
|
* Parse the model's answer DEFENSIVELY — anything unexpected is a
|
|
202
296
|
* failure, and a failure degrades honestly.
|
|
@@ -210,31 +304,41 @@ const SAFER_SYSTEM_PROMPT = [
|
|
|
210
304
|
*
|
|
211
305
|
* A fenced code block is the one accommodation, because models emit it
|
|
212
306
|
* constantly and it changes no content.
|
|
307
|
+
*
|
|
308
|
+
* R3v2-F1 widens that accommodation and NOTHING else. Unwrapping the
|
|
309
|
+
* named `alternatives` envelope, and reading `reason` as the spelling of
|
|
310
|
+
* `why` the prompt now asks for, are transport details: the entries that
|
|
311
|
+
* come out are verbatim the entries the model put in. That is the line
|
|
312
|
+
* between an accommodation and the salvage this parser refuses — a
|
|
313
|
+
* salvage changes WHICH alternatives are shown, and every rule that does
|
|
314
|
+
* that is still here. One bad entry still poisons the batch.
|
|
213
315
|
*/
|
|
214
316
|
export function parseSaferOptions(text) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (start < 0 || end <= start)
|
|
317
|
+
const body = jsonBody(text);
|
|
318
|
+
// truncation and absence part ways in saferFailureNote(), which reads
|
|
319
|
+
// the same scan; for the list itself both are the same nothing.
|
|
320
|
+
if (body === null || body === "truncated")
|
|
220
321
|
return null;
|
|
221
322
|
let parsed;
|
|
222
323
|
try {
|
|
223
|
-
parsed = JSON.parse(body
|
|
324
|
+
parsed = JSON.parse(body);
|
|
224
325
|
}
|
|
225
326
|
catch {
|
|
226
327
|
return null;
|
|
227
328
|
}
|
|
228
|
-
|
|
329
|
+
const envelope = typeof parsed === "object" && parsed !== null ? parsed.alternatives : undefined;
|
|
330
|
+
const list = Array.isArray(parsed) ? parsed : Array.isArray(envelope) ? envelope : null;
|
|
331
|
+
if (list === null || list.length === 0)
|
|
229
332
|
return null;
|
|
230
333
|
const out = [];
|
|
231
|
-
for (const item of
|
|
334
|
+
for (const item of list.slice(0, 3)) {
|
|
232
335
|
if (typeof item !== "object" || item === null)
|
|
233
336
|
return null;
|
|
234
|
-
const { command, why } = item;
|
|
337
|
+
const { command, reason, why } = item;
|
|
235
338
|
if (typeof command !== "string" || command.trim() === "")
|
|
236
339
|
return null;
|
|
237
|
-
|
|
340
|
+
const line = typeof reason === "string" ? reason : typeof why === "string" ? why : "";
|
|
341
|
+
out.push({ command: command.trim(), why: line.trim() });
|
|
238
342
|
}
|
|
239
343
|
return out.length === 0 ? null : out;
|
|
240
344
|
}
|
|
@@ -512,9 +616,13 @@ submitTurn) {
|
|
|
512
616
|
purpose: "safer-options",
|
|
513
617
|
systemPrompt: SAFER_SYSTEM_PROMPT,
|
|
514
618
|
prompt: `the pending call is: ${name} ${JSON.stringify(ev.input ?? {})}`,
|
|
515
|
-
maxTokens:
|
|
619
|
+
maxTokens: SAFER_MAX_TOKENS,
|
|
516
620
|
});
|
|
517
|
-
|
|
621
|
+
// R3v2-F1: a failure reports its CAUSE when the reply can
|
|
622
|
+
// prove one, so the panel can say which failure this was.
|
|
623
|
+
// saferFailure() returns null for every cause we cannot
|
|
624
|
+
// demonstrate, which is the unqualified line — unchanged.
|
|
625
|
+
return parseSaferOptions(answer) ?? saferFailure(answer);
|
|
518
626
|
};
|
|
519
627
|
const verdict = await askPanel(input, approvalView(name, ev, amendedCalls.has(name)), { safer });
|
|
520
628
|
amendedCalls.delete(name);
|
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
|
|
8
|
+
import { Dock, type AtItem, type Body, type PanelVerdict, type PanelView, type SaferAnswer, 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
|
|
@@ -82,9 +82,11 @@ export interface LineInput {
|
|
|
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
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.
|
|
85
|
+
* the trust gate), so those buttons cannot exist to be pressed.
|
|
86
|
+
* R3v2-F1: it resolves a SaferAnswer, so a failure that can name its
|
|
87
|
+
* cause does — `null` still means a failure with nothing to add. */
|
|
86
88
|
panelAsk(view: PanelView, onCommit: (v: PanelVerdict) => void, opts?: {
|
|
87
|
-
safer?: () => Promise<
|
|
89
|
+
safer?: () => Promise<SaferAnswer>;
|
|
88
90
|
}): void;
|
|
89
91
|
/** W21: cancel the panel — the SIGINT pair to panelAsk. */
|
|
90
92
|
panelCancel(): void;
|
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, type
|
|
8
|
+
import { type PanelVerdict, type PanelView, type SaferAnswer } 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";
|
|
@@ -35,7 +35,7 @@ import { type LineInput } from "./state.js";
|
|
|
35
35
|
*/
|
|
36
36
|
export declare let pendingAsk: (() => void) | null;
|
|
37
37
|
export declare function askPanel(input: LineInput, view: PanelView, opts?: {
|
|
38
|
-
safer?: () => Promise<
|
|
38
|
+
safer?: () => Promise<SaferAnswer>;
|
|
39
39
|
}): Promise<PanelVerdict>;
|
|
40
40
|
/**
|
|
41
41
|
* KC3.5 — the AskUI bridge: the panel the ask extension asks through.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.13.0",
|
|
22
|
+
"@vincemakes/kiso-core": "0.13.0",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.13.0",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.13.0",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.13.0",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.13.0",
|
|
27
|
+
"@vincemakes/kiso-runtime": "0.13.0",
|
|
28
|
+
"@vincemakes/kiso-skills-ext": "0.13.0",
|
|
29
|
+
"@vincemakes/kiso-subagent-ext": "0.13.0",
|
|
30
|
+
"@vincemakes/kiso-task-ext": "0.13.0",
|
|
31
|
+
"@vincemakes/kiso-tools-node": "0.13.0",
|
|
32
|
+
"@vincemakes/kiso-tui": "0.13.0",
|
|
33
|
+
"@vincemakes/kiso-tui-cells": "0.13.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.1.2",
|