@vincemakes/kiso-code 0.31.1 → 0.32.1
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/breaker.d.ts +57 -0
- package/dist/breaker.js +119 -0
- package/dist/chat.d.ts +21 -0
- package/dist/chat.js +74 -3
- package/dist/config.d.ts +3 -0
- package/dist/config.js +3 -0
- package/dist/dispatch.d.ts +24 -4
- package/dist/dispatch.js +195 -2
- package/dist/external-editor.d.ts +30 -0
- package/dist/external-editor.js +55 -0
- package/dist/index.js +190 -7
- package/dist/state.d.ts +35 -0
- package/dist/state.js +27 -0
- package/dist/trust-ui.js +111 -18
- package/package.json +16 -16
package/dist/state.d.ts
CHANGED
|
@@ -12,6 +12,14 @@ import type { KisoExtension, StoreRecord } from "@vincemakes/kiso-runtime";
|
|
|
12
12
|
* env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
|
|
13
13
|
* still override their own path; nothing hard-codes ~/.kiso anymore. */
|
|
14
14
|
export declare function kisoHome(): string;
|
|
15
|
+
/** DC-49 — the workspace the coding tools may touch, and the one place it
|
|
16
|
+
* is decided. The session's own tool set and the `!` command's runner both
|
|
17
|
+
* need it, and two copies of a ruling-bearing literal is how a later DC-49
|
|
18
|
+
* amendment reaches one caller and not the other. */
|
|
19
|
+
export declare function codingToolOptions(): {
|
|
20
|
+
readonly workspaceRoot: string;
|
|
21
|
+
readonly excludeRoots: readonly string[];
|
|
22
|
+
};
|
|
15
23
|
export declare function sessionsDir(): string;
|
|
16
24
|
/** E1: the extension scan directory — KISO_EXTENSIONS_DIR overrides. */
|
|
17
25
|
export declare function extensionsDir(): string;
|
|
@@ -77,6 +85,15 @@ export interface LineInput {
|
|
|
77
85
|
/** W15: the expand key (ctrl+o) — the chain-level action, never the
|
|
78
86
|
* editor's own interpretation. */
|
|
79
87
|
onExpand(cb: () => void): void;
|
|
88
|
+
/** §2.3: the thinking key (ctrl+t) — the same chain-level shape as
|
|
89
|
+
* onExpand. Optional: the readline input has no such key, and a
|
|
90
|
+
* dock-less session has nothing to reprint. */
|
|
91
|
+
onThink?(cb: () => void): void;
|
|
92
|
+
/** §2.4: the external-editor key (ctrl+g). */
|
|
93
|
+
onEditor?(cb: () => void): void;
|
|
94
|
+
/** §2.4: hand the terminal over, run `edit`, take it back. The editor
|
|
95
|
+
* owns the handover; the CLI owns the spawn. */
|
|
96
|
+
externalEdit?(edit: (text: string) => string | null): void;
|
|
80
97
|
/** E1 §3: the copy key (ctrl+x) — the chain-level action, never the
|
|
81
98
|
* composer's own. The readline input ignores it; `/copy` is the
|
|
82
99
|
* route that works everywhere. */
|
|
@@ -178,6 +195,24 @@ export declare let configModels: Readonly<Record<string, import("./config.js").M
|
|
|
178
195
|
export declare function setConfigModels(models: Readonly<Record<string, import("./config.js").ModelProfile>>): void;
|
|
179
196
|
/** The name of the model currently driving the session ("faux" or the
|
|
180
197
|
* profile name / provider/model write / env model). */
|
|
198
|
+
/**
|
|
199
|
+
* §2.5 — the profile the human CHOSE by name, and the only source a
|
|
200
|
+
* rebuild may read for the model.
|
|
201
|
+
*
|
|
202
|
+
* Not `currentModelName`: that holds whatever resolution produced, and
|
|
203
|
+
* an env-resolved session's name ("gpt-4o", "claude-sonnet-5") is not a
|
|
204
|
+
* key in `models`, so handing it back to `resolveModel` throws. Not the
|
|
205
|
+
* startup flag either, or a `/model` switch would silently revert on the
|
|
206
|
+
* first reload — the effort-axis round produced that bug once already
|
|
207
|
+
* from having two sources for the model.
|
|
208
|
+
*
|
|
209
|
+
* One variable, seeded by `makeAgent` from the startup flag and
|
|
210
|
+
* overwritten by `/model`. Undefined means "resolve exactly as this
|
|
211
|
+
* process did at startup", which is the right answer for a session that
|
|
212
|
+
* never named a profile.
|
|
213
|
+
*/
|
|
214
|
+
export declare let modelChoice: string | undefined;
|
|
215
|
+
export declare function setModelChoice(value: string | undefined): void;
|
|
181
216
|
export declare let currentModelName: string;
|
|
182
217
|
export declare function setCurrentModelName(value: string): void;
|
|
183
218
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
package/dist/state.js
CHANGED
|
@@ -18,6 +18,13 @@ import { AT_CAP, AT_SKIP, Dock } from "@vincemakes/kiso-tui";
|
|
|
18
18
|
export function kisoHome() {
|
|
19
19
|
return process.env.KISO_HOME ?? join(homedir(), ".kiso");
|
|
20
20
|
}
|
|
21
|
+
/** DC-49 — the workspace the coding tools may touch, and the one place it
|
|
22
|
+
* is decided. The session's own tool set and the `!` command's runner both
|
|
23
|
+
* need it, and two copies of a ruling-bearing literal is how a later DC-49
|
|
24
|
+
* amendment reaches one caller and not the other. */
|
|
25
|
+
export function codingToolOptions() {
|
|
26
|
+
return { workspaceRoot: process.cwd(), excludeRoots: [kisoHome()] };
|
|
27
|
+
}
|
|
21
28
|
export function sessionsDir() {
|
|
22
29
|
return join(kisoHome(), "sessions");
|
|
23
30
|
}
|
|
@@ -246,6 +253,26 @@ export function setConfigModels(models) {
|
|
|
246
253
|
}
|
|
247
254
|
/** The name of the model currently driving the session ("faux" or the
|
|
248
255
|
* profile name / provider/model write / env model). */
|
|
256
|
+
/**
|
|
257
|
+
* §2.5 — the profile the human CHOSE by name, and the only source a
|
|
258
|
+
* rebuild may read for the model.
|
|
259
|
+
*
|
|
260
|
+
* Not `currentModelName`: that holds whatever resolution produced, and
|
|
261
|
+
* an env-resolved session's name ("gpt-4o", "claude-sonnet-5") is not a
|
|
262
|
+
* key in `models`, so handing it back to `resolveModel` throws. Not the
|
|
263
|
+
* startup flag either, or a `/model` switch would silently revert on the
|
|
264
|
+
* first reload — the effort-axis round produced that bug once already
|
|
265
|
+
* from having two sources for the model.
|
|
266
|
+
*
|
|
267
|
+
* One variable, seeded by `makeAgent` from the startup flag and
|
|
268
|
+
* overwritten by `/model`. Undefined means "resolve exactly as this
|
|
269
|
+
* process did at startup", which is the right answer for a session that
|
|
270
|
+
* never named a profile.
|
|
271
|
+
*/
|
|
272
|
+
export let modelChoice;
|
|
273
|
+
export function setModelChoice(value) {
|
|
274
|
+
modelChoice = value;
|
|
275
|
+
}
|
|
249
276
|
export let currentModelName = "faux";
|
|
250
277
|
export function setCurrentModelName(value) {
|
|
251
278
|
currentModelName = value;
|
package/dist/trust-ui.js
CHANGED
|
@@ -162,17 +162,38 @@ export function askUi(input) {
|
|
|
162
162
|
export async function addDontAskAgainRule(rule) {
|
|
163
163
|
const dir = extensionsDir();
|
|
164
164
|
const file = join(dir, "dont-ask-again.mjs");
|
|
165
|
-
|
|
165
|
+
// §2.5: the LIVE extension, found by name in the array the chain reads.
|
|
166
|
+
// This is the only handle on the rules that is guaranteed to be the one
|
|
167
|
+
// in force, and finding it also tells us whether this is a first rule.
|
|
168
|
+
const at = currentAgentExtensions.findIndex((e) => e.name === "dont-ask-again");
|
|
169
|
+
const live = at >= 0 ? currentAgentExtensions[at] : undefined;
|
|
170
|
+
// Every rule the session already grants, plus this one (RL-F2: the file
|
|
171
|
+
// used to be rewritten with ONLY the newest rule, so a second grant
|
|
172
|
+
// erased the first from disk — invisible in-session, because the
|
|
173
|
+
// in-memory Set kept both, and only discovered at the next start).
|
|
174
|
+
//
|
|
175
|
+
// RL-F5: a file generated BEFORE this change exports RULES and puts
|
|
176
|
+
// nothing on the extension, so `live.rules` is undefined for it and a
|
|
177
|
+
// union starting from nothing would rewrite the file with this grant
|
|
178
|
+
// alone — every rule the human gave under 0.31.x gone, in exactly the
|
|
179
|
+
// way RL-F2 was invisible. The rules come out of the file instead, and
|
|
180
|
+
// the migration costs one grant.
|
|
181
|
+
const existing = live?.rules !== undefined ? [...live.rules] : rulesInFile(file);
|
|
182
|
+
const rules = [...new Set([...existing, rule])].sort();
|
|
166
183
|
const module = [
|
|
167
184
|
"// generated by kiso — the \"2 Yes, don't ask again for <tool>\" rule.",
|
|
168
185
|
"// HUMAN-EDITABLE and HUMAN-DELETABLE — this file IS the revocation path:",
|
|
169
186
|
"// delete a rule from the set (or the whole file) to be asked again.",
|
|
170
187
|
"// ALLOW-ONLY by design (R3): never emits deny or ask — the mode moat",
|
|
171
188
|
"// and the safe-defaults moat keep their teeth.",
|
|
172
|
-
`
|
|
189
|
+
`const RULES = new Set(${JSON.stringify(rules)});`,
|
|
173
190
|
"",
|
|
174
191
|
"export default {",
|
|
175
192
|
' name: "dont-ask-again",',
|
|
193
|
+
" // the live handle: kiso mutates THIS set to make a new rule take",
|
|
194
|
+
" // effect without a reload. It is the same object the decide below",
|
|
195
|
+
" // closes over, which is the whole point.",
|
|
196
|
+
" rules: RULES,",
|
|
176
197
|
" approvals: [",
|
|
177
198
|
" {",
|
|
178
199
|
" decide(call) {",
|
|
@@ -185,21 +206,57 @@ export async function addDontAskAgainRule(rule) {
|
|
|
185
206
|
].join("\n");
|
|
186
207
|
mkdirSync(dir, { recursive: true });
|
|
187
208
|
writeFileSync(file, module, "utf8");
|
|
188
|
-
// The live in-session effect
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
if (
|
|
209
|
+
// The live in-session effect. This used to re-import the plain file URL
|
|
210
|
+
// and mutate the module's exported Set, on the stated assumption that a
|
|
211
|
+
// file the loader imported is ONE namespace. §2.5's loader appends a
|
|
212
|
+
// per-load nonce so `/reload` can pick up edited code, and that
|
|
213
|
+
// assumption died with it: the loader holds `file?load=N` and a plain
|
|
214
|
+
// import is the startup namespace. A rule granted after any reload
|
|
215
|
+
// would mutate a Set nothing reads — the human clicks don't-ask-again
|
|
216
|
+
// and is asked again on the very next identical call.
|
|
217
|
+
//
|
|
218
|
+
// So there is no second import. The rule is added THROUGH the live
|
|
219
|
+
// extension, which is one source of truth by construction.
|
|
220
|
+
if (live?.rules !== undefined) {
|
|
221
|
+
live.rules.add(rule);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Either a FIRST rule (no such extension loaded) or the RL-F5 migration
|
|
225
|
+
// (a legacy extension is loaded but has no live handle). The freshly
|
|
226
|
+
// written file is imported and joins the chain — REPLACING the legacy
|
|
227
|
+
// entry rather than sitting beside it, because two extensions of the
|
|
228
|
+
// same name in the chain is a second source of truth and the whole point
|
|
229
|
+
// of the handle is that there is one. The chain is fixed at a run's
|
|
230
|
+
// start, so a first rule applies from the NEXT run (run.ts re-reads the
|
|
231
|
+
// session config's extensions array, shared by reference).
|
|
232
|
+
//
|
|
233
|
+
// The nonce is the loader's own lesson applied here: a plain file URL is
|
|
234
|
+
// cached forever, so a second grant in one process would import the
|
|
235
|
+
// bytes of the first.
|
|
236
|
+
const mod = (await import(`${pathToFileURL(file).href}?grant=${(grantSerial += 1)}`));
|
|
237
|
+
if (mod.default === undefined)
|
|
238
|
+
return;
|
|
239
|
+
if (at >= 0)
|
|
240
|
+
currentAgentExtensions[at] = mod.default;
|
|
241
|
+
else
|
|
200
242
|
currentAgentExtensions.push(mod.default);
|
|
243
|
+
}
|
|
244
|
+
/** RL-F5 — the rules a generated file already holds. The `new Set([...])`
|
|
245
|
+
* literal is JSON, which is what makes reading it back honest rather than
|
|
246
|
+
* a parser. An unreadable or hand-edited-past-recognition file yields
|
|
247
|
+
* nothing, and the new rule stands alone — the file is documented as
|
|
248
|
+
* human-editable, so kiso must not fail on what a human wrote there. */
|
|
249
|
+
function rulesInFile(file) {
|
|
250
|
+
try {
|
|
251
|
+
const m = /new Set\((\[[^\]]*\])\)/.exec(readFileSync(file, "utf8"));
|
|
252
|
+
return m === null ? [] : JSON.parse(m[1]);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return [];
|
|
201
256
|
}
|
|
202
257
|
}
|
|
258
|
+
/** RL-F5: distinguishes each grant's import of the generated file. */
|
|
259
|
+
let grantSerial = 0;
|
|
203
260
|
/**
|
|
204
261
|
* W21 — the §3.5 fix-hint table: per speaker, the ONE-line path to stop
|
|
205
262
|
* being asked. The hint names the FIX, never a bypass ("switch to
|
|
@@ -309,8 +366,24 @@ function readMcpConfig(path) {
|
|
|
309
366
|
}
|
|
310
367
|
/** Merge user-level + project-level mcp.json into one temp file and point
|
|
311
368
|
* KISO_MCP_CONFIG at it — the mcp extension reads it at load time. */
|
|
369
|
+
/**
|
|
370
|
+
* RL-F4 — the user mcp.json as it was BEFORE any merge, and RL-F1's exact
|
|
371
|
+
* sibling one function up. `applyMcpMerge` read `KISO_MCP_CONFIG` and then
|
|
372
|
+
* assigned the merged file to it, so a second merge — which is what a
|
|
373
|
+
* reload is — took the first merged file as the user config. That file
|
|
374
|
+
* already holds the project's servers, so the "exists in both" check
|
|
375
|
+
* throws for every one of them and EVERY reload fails in exactly the
|
|
376
|
+
* projects that carry MCP servers.
|
|
377
|
+
*
|
|
378
|
+
* Found by the lead reviewing the commit that fixed RL-F1 in the function
|
|
379
|
+
* below. The lesson is the sibling, not the loop: a defect found in one
|
|
380
|
+
* function is a question to ask of every function beside it, and I fixed
|
|
381
|
+
* one merge without looking at the other.
|
|
382
|
+
*/
|
|
383
|
+
let originalUserMcpPath;
|
|
312
384
|
function applyMcpMerge(root) {
|
|
313
|
-
|
|
385
|
+
originalUserMcpPath ??= process.env.KISO_MCP_CONFIG ?? join(kisoHome(), "mcp.json");
|
|
386
|
+
const userPath = originalUserMcpPath;
|
|
314
387
|
const user = readMcpConfig(userPath);
|
|
315
388
|
const project = readMcpConfig(join(root, "mcp.json"));
|
|
316
389
|
const userServers = user.mcpServers ?? {};
|
|
@@ -323,17 +396,37 @@ function applyMcpMerge(root) {
|
|
|
323
396
|
}
|
|
324
397
|
}
|
|
325
398
|
const merged = { mcpServers: { ...userServers, ...projectServers } };
|
|
326
|
-
|
|
399
|
+
// RL-F4, second half: the path used to be `kiso-mcp-merged-<pid>.json`,
|
|
400
|
+
// the SAME file on every merge in one process — so a reload's cleanup of
|
|
401
|
+
// the paths the previous set owned deleted the file the new set had just
|
|
402
|
+
// been merged into. One directory per merge, and the directory is what is
|
|
403
|
+
// recorded, so the cleanup removes exactly what that merge created.
|
|
404
|
+
const dir = mkdtempSync(join(tmpdir(), "kiso-mcp-"));
|
|
405
|
+
const temp = join(dir, "mcp.json");
|
|
327
406
|
writeFileSync(temp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
328
407
|
process.env.KISO_MCP_CONFIG = temp;
|
|
329
|
-
mergedTempPaths.push(
|
|
408
|
+
mergedTempPaths.push(dir);
|
|
330
409
|
}
|
|
331
410
|
/** Merge user-level + project-level skills into one temp scan dir (project
|
|
332
411
|
* skill dirs symlinked first; a name in both → project wins + a stderr
|
|
333
412
|
* note) and point KISO_SKILLS_DIR at it — the skills extension's existing
|
|
334
413
|
* scan reads it at load time and per read_skill call. */
|
|
414
|
+
/**
|
|
415
|
+
* §2.5 — the user skills directory as it was BEFORE any merge.
|
|
416
|
+
*
|
|
417
|
+
* `applySkillsMerge` ends by pointing `KISO_SKILLS_DIR` at the merged
|
|
418
|
+
* directory it just made. Read twice — which is exactly what a reload
|
|
419
|
+
* does — that is a loop: the second merge takes the FIRST merge as its
|
|
420
|
+
* user directory. A skill deleted from the real user directory would
|
|
421
|
+
* survive forever, and each merge would produce a temp directory the
|
|
422
|
+
* previous one's symlinks depend on, so none of them could ever be
|
|
423
|
+
* removed. This tree has already lost an afternoon to 449,956 stale
|
|
424
|
+
* temp directories.
|
|
425
|
+
*/
|
|
426
|
+
let originalUserSkillsDir;
|
|
335
427
|
function applySkillsMerge(root) {
|
|
336
|
-
|
|
428
|
+
originalUserSkillsDir ??= process.env.KISO_SKILLS_DIR ?? join(kisoHome(), "skills");
|
|
429
|
+
const userDir = originalUserSkillsDir;
|
|
337
430
|
const projectDir = join(root, "skills");
|
|
338
431
|
const merged = mkdtempSync(join(tmpdir(), "kiso-skills-"));
|
|
339
432
|
mergedTempPaths.push(merged);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "kiso
|
|
3
|
+
"version": "0.32.1",
|
|
4
|
+
"description": "kiso-code — the coding agent on kiso, the durable runtime for AI agents: kiso chat / kiso resume / kiso sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -18,20 +18,20 @@
|
|
|
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-provider-openai-responses": "0.
|
|
28
|
-
"@vincemakes/kiso-runtime": "0.
|
|
29
|
-
"@vincemakes/kiso-skills-ext": "0.
|
|
30
|
-
"@vincemakes/kiso-subagent-ext": "0.
|
|
31
|
-
"@vincemakes/kiso-task-ext": "0.
|
|
32
|
-
"@vincemakes/kiso-tools-node": "0.
|
|
33
|
-
"@vincemakes/kiso-tui": "0.
|
|
34
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
21
|
+
"@vincemakes/kiso-ask-ext": "0.32.1",
|
|
22
|
+
"@vincemakes/kiso-core": "0.32.1",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.32.1",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.32.1",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.32.1",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.32.1",
|
|
27
|
+
"@vincemakes/kiso-provider-openai-responses": "0.32.1",
|
|
28
|
+
"@vincemakes/kiso-runtime": "0.32.1",
|
|
29
|
+
"@vincemakes/kiso-skills-ext": "0.32.1",
|
|
30
|
+
"@vincemakes/kiso-subagent-ext": "0.32.1",
|
|
31
|
+
"@vincemakes/kiso-task-ext": "0.32.1",
|
|
32
|
+
"@vincemakes/kiso-tools-node": "0.32.1",
|
|
33
|
+
"@vincemakes/kiso-tui": "0.32.1",
|
|
34
|
+
"@vincemakes/kiso-tui-cells": "0.32.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^26.1.2",
|