@vincemakes/kiso-code 0.39.1 → 0.40.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/README.md +1 -1
- package/dist/attachments.d.ts +1 -1
- package/dist/attachments.js +11 -4
- package/dist/builtin.d.ts +12 -0
- package/dist/builtin.js +22 -1
- package/dist/chat.d.ts +28 -2
- package/dist/chat.js +143 -25
- package/dist/config.d.ts +11 -1
- package/dist/config.js +48 -3
- package/dist/dispatch.d.ts +10 -2
- package/dist/dispatch.js +138 -23
- package/dist/floor.d.ts +80 -0
- package/dist/floor.js +652 -0
- package/dist/index.d.ts +21 -2
- package/dist/index.js +397 -44
- package/dist/mode.d.ts +12 -4
- package/dist/mode.js +32 -10
- package/dist/projects.d.ts +106 -0
- package/dist/projects.js +212 -0
- package/dist/protected-shell.d.ts +88 -0
- package/dist/protected-shell.js +406 -0
- package/dist/protected-writes.d.ts +29 -0
- package/dist/protected-writes.js +54 -0
- package/dist/readonly-shell.d.ts +58 -0
- package/dist/readonly-shell.js +590 -0
- package/dist/replay.d.ts +53 -0
- package/dist/replay.js +158 -0
- package/dist/resume-tail.js +4 -1
- package/dist/resume.js +3 -3
- package/dist/retries.d.ts +9 -0
- package/dist/retries.js +16 -0
- package/dist/session-cards.d.ts +33 -4
- package/dist/session-cards.js +44 -3
- package/dist/session-migration.d.ts +104 -0
- package/dist/session-migration.js +319 -0
- package/dist/shell-words.d.ts +134 -0
- package/dist/shell-words.js +628 -0
- package/dist/skill-invoke.d.ts +58 -0
- package/dist/skill-invoke.js +139 -0
- package/dist/state.d.ts +67 -1
- package/dist/state.js +98 -2
- package/dist/trust-ui.js +29 -5
- package/package.json +15 -15
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.40.0 — a PERSON invokes a skill.
|
|
3
|
+
*
|
|
4
|
+
* `/skill <name> [args…]`, or `/<name> [args…]` when no built-in command
|
|
5
|
+
* has that name — built-ins win, always, so installing a skill can never
|
|
6
|
+
* take a command away. The invocation is a USER TURN: the SKILL.md body,
|
|
7
|
+
* then the person's args after a blank line, submitted through the same
|
|
8
|
+
* path as typed input. The durable record says the turn came from the
|
|
9
|
+
* person (`source` stays "user") and says HOW it was composed in `via`,
|
|
10
|
+
* which carries the line they typed — so the chip, the resume tail and the
|
|
11
|
+
* session's name show what the person wrote, not a body they never saw.
|
|
12
|
+
*
|
|
13
|
+
* Pure: the catalog comes from the skills extension's own scan (one scan,
|
|
14
|
+
* one answer — the model's index and this door are one list), and every
|
|
15
|
+
* outcome is data. The dispatcher does the printing and the submitting.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* What a submitted line asks of the skills, if anything. `null` means the
|
|
19
|
+
* line is not a skill request, and the dispatcher goes on as before — which
|
|
20
|
+
* is how a typo stays "unknown command" rather than becoming a skill error.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveSkillLine(trimmed, catalog, builtins) {
|
|
23
|
+
if (trimmed.includes("\n") || !trimmed.startsWith("/"))
|
|
24
|
+
return null;
|
|
25
|
+
const [word = "", ...rest] = trimmed.split(/\s+/);
|
|
26
|
+
if (word === "/skills")
|
|
27
|
+
return rest.length === 0 ? { kind: "list" } : { kind: "error", message: "usage: /skills (no arguments) — /skill <name> [args] runs one" };
|
|
28
|
+
if (word === "/skill") {
|
|
29
|
+
const [name = ""] = rest;
|
|
30
|
+
if (name === "")
|
|
31
|
+
return { kind: "error", message: "usage: /skill <name> [args…] — /skills lists them" };
|
|
32
|
+
return invoke(name, argsOf(trimmed, 2), trimmed, catalog);
|
|
33
|
+
}
|
|
34
|
+
// `/<name>`: only a word no built-in claims, and only a skill that
|
|
35
|
+
// exists — anything else is the dispatcher's own "unknown command".
|
|
36
|
+
if (builtins.includes(word))
|
|
37
|
+
return null;
|
|
38
|
+
const name = word.slice(1);
|
|
39
|
+
if (catalog === null || (!catalog.entries.some((e) => e.name === name) && !catalog.broken.some((b) => b.dir === name)))
|
|
40
|
+
return null;
|
|
41
|
+
return invoke(name, argsOf(trimmed, 1), trimmed, catalog);
|
|
42
|
+
}
|
|
43
|
+
/** The args as typed — everything after the first `words` words, with its
|
|
44
|
+
* own spacing kept. A skill that asks for a path wants the path, not a
|
|
45
|
+
* re-joined approximation of it. */
|
|
46
|
+
function argsOf(trimmed, words) {
|
|
47
|
+
let rest = trimmed;
|
|
48
|
+
for (let i = 0; i < words; i += 1)
|
|
49
|
+
rest = rest.replace(/^\S+\s*/, "");
|
|
50
|
+
return rest;
|
|
51
|
+
}
|
|
52
|
+
function invoke(name, args, line, catalog) {
|
|
53
|
+
const entry = catalog?.entries.find((e) => e.name === name);
|
|
54
|
+
if (entry === undefined) {
|
|
55
|
+
const broken = catalog?.broken.find((b) => b.dir === name);
|
|
56
|
+
if (broken !== undefined)
|
|
57
|
+
return { kind: "error", message: `skill "${name}" cannot load: ${broken.reason}` };
|
|
58
|
+
const near = nearest(name, catalog?.entries.map((e) => e.name) ?? []);
|
|
59
|
+
const hint = near.length > 0 ? ` — nearest: ${near.join(", ")}` : "";
|
|
60
|
+
return { kind: "error", message: `no skill named "${name}"${hint} (/skills lists them)` };
|
|
61
|
+
}
|
|
62
|
+
if (!entry.userInvocable)
|
|
63
|
+
return { kind: "error", message: `skill "${name}" is for the model only (user-invocable: false)` };
|
|
64
|
+
const read = catalog.body(name);
|
|
65
|
+
if ("error" in read)
|
|
66
|
+
return { kind: "error", message: `skill "${name}" cannot load: ${read.error}` };
|
|
67
|
+
return {
|
|
68
|
+
kind: "submit",
|
|
69
|
+
content: args === "" ? read.body : `${read.body}\n\n${args}`,
|
|
70
|
+
via: { kind: "skill", name, line },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Up to three installed names close to the one typed: a shared prefix, or
|
|
74
|
+
* within two edits. Closest first; nothing when nothing is close — a
|
|
75
|
+
* suggestion that is not near is noise. */
|
|
76
|
+
export function nearest(name, names) {
|
|
77
|
+
const scored = names
|
|
78
|
+
.map((n) => ({ n, d: n.startsWith(name) || name.startsWith(n) ? 0 : distance(name, n) }))
|
|
79
|
+
.filter((s) => s.d <= 2)
|
|
80
|
+
.sort((a, b) => a.d - b.d || (a.n < b.n ? -1 : 1));
|
|
81
|
+
return scored.slice(0, 3).map((s) => s.n);
|
|
82
|
+
}
|
|
83
|
+
function distance(a, b) {
|
|
84
|
+
const row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
85
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
86
|
+
let prev = row[0];
|
|
87
|
+
row[0] = i;
|
|
88
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
89
|
+
const cur = row[j];
|
|
90
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
91
|
+
prev = cur;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return row[b.length];
|
|
95
|
+
}
|
|
96
|
+
/** `/skills` — the skills grouped by the directory they really live in,
|
|
97
|
+
* each directory named ONCE as a header: project and user skills are
|
|
98
|
+
* merged into one scan directory by symlink, so the scan root would name a
|
|
99
|
+
* temp directory nobody can find — `sourceOf` resolves an entry's
|
|
100
|
+
* directory to where it came from. Under each: `/name — description`, a
|
|
101
|
+
* `model only` tag for `user-invocable: false`, then the broken entries
|
|
102
|
+
* with the loader's own reason. */
|
|
103
|
+
export function skillsRows(catalog, sourceOf, userDir) {
|
|
104
|
+
const entries = catalog?.entries ?? [];
|
|
105
|
+
const broken = catalog?.broken ?? [];
|
|
106
|
+
if (entries.length === 0 && broken.length === 0)
|
|
107
|
+
return [`no skills installed — add one as ${userDir}/<name>/SKILL.md`];
|
|
108
|
+
const groups = new Map();
|
|
109
|
+
const add = (dir, row) => {
|
|
110
|
+
const src = sourceOf(dir);
|
|
111
|
+
groups.set(src, [...(groups.get(src) ?? []), row]);
|
|
112
|
+
};
|
|
113
|
+
for (const e of entries)
|
|
114
|
+
add(e.dir, ` /${e.name} — ${e.description}${e.userInvocable ? "" : " (model only)"}`);
|
|
115
|
+
for (const b of broken)
|
|
116
|
+
add(b.dir, ` ${b.dir} — cannot load: ${b.reason}`);
|
|
117
|
+
const rows = [];
|
|
118
|
+
for (const [src, list] of groups)
|
|
119
|
+
rows.push(src, ...list);
|
|
120
|
+
rows.push("/<name> [args] or /skill <name> [args] runs one · a built-in command wins a shared name");
|
|
121
|
+
return rows;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 0.40.1 (owner's ruling, revoking the 0.41.0 deferral) — the installed
|
|
125
|
+
* skills as `/` menu entries. The same rule the dispatcher follows, so the
|
|
126
|
+
* menu never offers what `/<name>` would not run: invocable skills only
|
|
127
|
+
* (`user-invocable: false` stays the model's), a name a person can type
|
|
128
|
+
* after `/`, and never a name a built-in claims — the built-in wins. The
|
|
129
|
+
* description is cut so a long one cannot crowd the band.
|
|
130
|
+
*/
|
|
131
|
+
export function skillMenuItems(catalog, builtins) {
|
|
132
|
+
if (catalog === null)
|
|
133
|
+
return [];
|
|
134
|
+
const taken = new Set(builtins);
|
|
135
|
+
const cut = (text, n) => (text.length <= n ? text : `${text.slice(0, n - 1)}…`);
|
|
136
|
+
return catalog.entries
|
|
137
|
+
.filter((e) => e.userInvocable && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(e.name) && !taken.has(`/${e.name}`))
|
|
138
|
+
.map((e) => ({ name: `/${e.name}`, desc: `${cut(e.description, 56)} · skill` }));
|
|
139
|
+
}
|
package/dist/state.d.ts
CHANGED
|
@@ -29,7 +29,40 @@ export declare function codingToolOptions(): {
|
|
|
29
29
|
readonly workspaceRoot: string;
|
|
30
30
|
readonly excludeRoots: readonly string[];
|
|
31
31
|
readonly secretEnvNames: readonly string[];
|
|
32
|
+
readonly protectedFiles: readonly string[];
|
|
32
33
|
};
|
|
34
|
+
export declare function setUserProtectedPaths(paths: readonly string[] | undefined): void;
|
|
35
|
+
/** kiso never serves its own credential store to a model. The store is
|
|
36
|
+
* `auth.json` under KISO_HOME — the path auth/credentials.ts writes (a
|
|
37
|
+
* test holds the two together); its temp file and lock are covered by
|
|
38
|
+
* the match rule (tools-node protected.ts). Then the user's own list.
|
|
39
|
+
* ONE list for every reader: the file tools, the search, the shell
|
|
40
|
+
* check and the `!` gesture. */
|
|
41
|
+
export declare function protectedFiles(): readonly string[];
|
|
42
|
+
/** 0.40.0 — the workspace a session records: the realpath of where kiso
|
|
43
|
+
* runs, so `/a/link` and `/a/target` are one workspace (and one folder). */
|
|
44
|
+
export declare function workspaceRoot(): string;
|
|
45
|
+
/** 0.40.0 — the project a session folder belongs to: the cwd as the disk
|
|
46
|
+
* spells it (projects.ts canonicalPath). Only the folder's identity uses
|
|
47
|
+
* it; the tools keep workspaceRoot(). */
|
|
48
|
+
export declare function projectRoot(): string;
|
|
49
|
+
/**
|
|
50
|
+
* 0.40.0 (the owner's dogfood) — this project's session folder
|
|
51
|
+
* (projects.ts). `KISO_SESSIONS_DIR` pins it (tests, bench runners, a
|
|
52
|
+
* delegated child beside its parent), and a reversed migration restores
|
|
53
|
+
* the single legacy folder.
|
|
54
|
+
*
|
|
55
|
+
* A pure read — the folder is claimed (made, and its workspace recorded)
|
|
56
|
+
* only after the trust gate, where the store is built.
|
|
57
|
+
*/
|
|
58
|
+
export declare function ownSessionsDir(): string;
|
|
59
|
+
/** 0.40.0 (the lead's ruling) — a session from `_unknown`, or placed in
|
|
60
|
+
* another project's folder only by inference, resumes WHERE IT IS: a
|
|
61
|
+
* resume never moves a session. The store is then built on that folder,
|
|
62
|
+
* and null returns this process to its own. */
|
|
63
|
+
export declare function setOpenSessionFolder(dir: string | null): void;
|
|
64
|
+
/** The folder of the session this process has open: its own project's,
|
|
65
|
+
* or the one an unknown or inferred session was resumed in. */
|
|
33
66
|
export declare function sessionsDir(): string;
|
|
34
67
|
/** E1: the extension scan directory — KISO_EXTENSIONS_DIR overrides. */
|
|
35
68
|
export declare function extensionsDir(): string;
|
|
@@ -141,7 +174,7 @@ export interface LineInput {
|
|
|
141
174
|
* picked id, or null when the human leaves without picking.
|
|
142
175
|
* OPTIONAL: only the raw-mode editor has it, and the picker only
|
|
143
176
|
* ever opens on a TTY (a pipe has nobody to pick). */
|
|
144
|
-
pick?(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void): void;
|
|
177
|
+
pick?(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void, here?: string): void;
|
|
145
178
|
/** W22: bind the pending-turn queue — the ↑ pop walks the CLI's
|
|
146
179
|
* live slots (each pop cancels the turn), esc ends the walk after
|
|
147
180
|
* one more pop. The chips are the compositor's own bindQueue. */
|
|
@@ -195,6 +228,23 @@ export declare function setCurrentFaux(value: boolean): void;
|
|
|
195
228
|
* LAST makeAgent — /model and autoCompact resolve against it. */
|
|
196
229
|
export declare let mergedConfig: import("./config.js").KisoConfig;
|
|
197
230
|
export declare function setMergedConfig(value: import("./config.js").KisoConfig): void;
|
|
231
|
+
/** ADR-0005 Amendment 2: the retry the kernel announced and is waiting
|
|
232
|
+
* on, for the running and compacting rows — `until` is the wall-clock
|
|
233
|
+
* end of the wait. Null when no retry is pending. Cleared by the next
|
|
234
|
+
* event from the run, which means the attempt got through, and wherever
|
|
235
|
+
* a run or a compaction starts and ends. */
|
|
236
|
+
export interface RetryShown {
|
|
237
|
+
readonly attempt: number;
|
|
238
|
+
readonly maxRetries: number;
|
|
239
|
+
readonly code: string;
|
|
240
|
+
readonly until: number;
|
|
241
|
+
}
|
|
242
|
+
export declare let retryShown: RetryShown | null;
|
|
243
|
+
export declare function setRetryShown(value: RetryShown | null): void;
|
|
244
|
+
/** The pending retry as a row shows it, with the time left computed NOW:
|
|
245
|
+
* the rows repaint on their own timers, so the countdown moves between
|
|
246
|
+
* the kernel's announcements. */
|
|
247
|
+
export declare function retryOnRow(): import("@vincemakes/kiso-tui").RetryOnRow | null;
|
|
198
248
|
/** merge round B: the resolved context window (env > config.contextWindow) —
|
|
199
249
|
* chat.ts's contextWindowTokens() consults it before the env. */
|
|
200
250
|
export declare let configuredWindow: number | undefined;
|
|
@@ -226,6 +276,14 @@ export declare function setModelChoice(value: string | undefined): void;
|
|
|
226
276
|
export declare let currentModelName: string;
|
|
227
277
|
export declare function setCurrentModelName(value: string): void;
|
|
228
278
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
|
279
|
+
/** 0.40.0: whether the catastrophe floor is on (floor.ts) — the user
|
|
280
|
+
* config's `floor`, read where the chain is assembled. */
|
|
281
|
+
export declare let floorOn: boolean;
|
|
282
|
+
export declare function setFloorOn(value: boolean): void;
|
|
283
|
+
/** 0.40.0: the calls a saved allow never carries (protected-writes.ts) —
|
|
284
|
+
* set where the chain is assembled, read where a first grant joins it. */
|
|
285
|
+
export declare let neverInherited: (call: import("@vincemakes/kiso-core").PolicyCall) => boolean;
|
|
286
|
+
export declare function setNeverInherited(value: (call: import("@vincemakes/kiso-core").PolicyCall) => boolean): void;
|
|
229
287
|
export declare let loadedExtensions: readonly KisoExtension[];
|
|
230
288
|
/** R-D 0.1.45: the BUILT-IN layer — the three default official extensions,
|
|
231
289
|
* shipped with the cli (module imports, never a disk scan; builtin.ts).
|
|
@@ -239,6 +297,11 @@ export declare let userExtensions: readonly KisoExtension[];
|
|
|
239
297
|
* banner distinguishes them from the user-level ones. */
|
|
240
298
|
export declare let projectExtensions: readonly KisoExtension[];
|
|
241
299
|
export declare function setExtensionLists(builtIn: readonly KisoExtension[], user: readonly KisoExtension[], project: readonly KisoExtension[], loaded: readonly KisoExtension[]): void;
|
|
300
|
+
/** 0.40.0: the skills catalog of the loaded skills extension — the scan the
|
|
301
|
+
* model's index came from, so `/skill` and `/skills` cannot list a
|
|
302
|
+
* different set. Null when no skills extension is loaded (or a user
|
|
303
|
+
* extension named "skills" shadows it and reports none). */
|
|
304
|
+
export declare function loadedSkillsCatalog(): import("@vincemakes/kiso-skills-ext").SkillsCatalog | null;
|
|
242
305
|
/** W21: the CURRENT agent's extensions array — set by makeAgent, the
|
|
243
306
|
* don't-ask-again writer pushes the generated extension into it so a
|
|
244
307
|
* first-time rule joins the chain at the NEXT run (the run's policies
|
|
@@ -266,6 +329,9 @@ export interface LastBinding {
|
|
|
266
329
|
readonly baseUrl?: string;
|
|
267
330
|
readonly scope?: import("@vincemakes/kiso-core").ContinuationScope;
|
|
268
331
|
readonly reasoning?: import("@vincemakes/kiso-runtime/internal").ReasoningSetting;
|
|
332
|
+
/** 0.40.0: the config profile that named the binding (null for a direct
|
|
333
|
+
* provider/model) — a /clear-fresh session records it too. */
|
|
334
|
+
readonly profileName?: string | null;
|
|
269
335
|
}
|
|
270
336
|
export declare function setLastBinding(b: LastBinding): void;
|
|
271
337
|
export declare function lastBinding(): LastBinding | null;
|
package/dist/state.js
CHANGED
|
@@ -11,6 +11,7 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { AT_CAP, AT_SKIP, Dock } from "@vincemakes/kiso-tui";
|
|
14
|
+
import { canonicalPath, LEGACY_SESSIONS_DIR, projectDirFor, projectLayoutActive } from "./projects.js";
|
|
14
15
|
/** finding #11: KISO_HOME is the ONE root — every default path derives from
|
|
15
16
|
* it (sessions, trust, extensions, mcp config, skills). The dedicated
|
|
16
17
|
* env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
|
|
@@ -38,10 +39,74 @@ export function secretEnvNamesOf(models) {
|
|
|
38
39
|
];
|
|
39
40
|
}
|
|
40
41
|
export function codingToolOptions() {
|
|
41
|
-
return { workspaceRoot: process.cwd(), excludeRoots: [kisoHome()], secretEnvNames: secretEnvNamesOf(configModels) };
|
|
42
|
+
return { workspaceRoot: process.cwd(), excludeRoots: [kisoHome()], secretEnvNames: secretEnvNamesOf(configModels), protectedFiles: protectedFiles() };
|
|
42
43
|
}
|
|
44
|
+
/** The user config's `protectedPaths`, as read at the agent's build (and
|
|
45
|
+
* again at /reload) — `~/` taken against the home directory. */
|
|
46
|
+
let userProtectedPaths = [];
|
|
47
|
+
export function setUserProtectedPaths(paths) {
|
|
48
|
+
userProtectedPaths = (paths ?? []).map((p) => (p.startsWith("~/") ? join(homedir(), p.slice(2)) : p));
|
|
49
|
+
}
|
|
50
|
+
/** kiso never serves its own credential store to a model. The store is
|
|
51
|
+
* `auth.json` under KISO_HOME — the path auth/credentials.ts writes (a
|
|
52
|
+
* test holds the two together); its temp file and lock are covered by
|
|
53
|
+
* the match rule (tools-node protected.ts). Then the user's own list.
|
|
54
|
+
* ONE list for every reader: the file tools, the search, the shell
|
|
55
|
+
* check and the `!` gesture. */
|
|
56
|
+
export function protectedFiles() {
|
|
57
|
+
return [join(kisoHome(), "auth.json"), ...userProtectedPaths];
|
|
58
|
+
}
|
|
59
|
+
/** 0.40.0 — the workspace a session records: the realpath of where kiso
|
|
60
|
+
* runs, so `/a/link` and `/a/target` are one workspace (and one folder). */
|
|
61
|
+
export function workspaceRoot() {
|
|
62
|
+
try {
|
|
63
|
+
return realpathSync(process.cwd());
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return process.cwd();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** 0.40.0 — the project a session folder belongs to: the cwd as the disk
|
|
70
|
+
* spells it (projects.ts canonicalPath). Only the folder's identity uses
|
|
71
|
+
* it; the tools keep workspaceRoot(). */
|
|
72
|
+
export function projectRoot() {
|
|
73
|
+
return canonicalPath(process.cwd());
|
|
74
|
+
}
|
|
75
|
+
let projectSessions = null;
|
|
76
|
+
/**
|
|
77
|
+
* 0.40.0 (the owner's dogfood) — this project's session folder
|
|
78
|
+
* (projects.ts). `KISO_SESSIONS_DIR` pins it (tests, bench runners, a
|
|
79
|
+
* delegated child beside its parent), and a reversed migration restores
|
|
80
|
+
* the single legacy folder.
|
|
81
|
+
*
|
|
82
|
+
* A pure read — the folder is claimed (made, and its workspace recorded)
|
|
83
|
+
* only after the trust gate, where the store is built.
|
|
84
|
+
*/
|
|
85
|
+
export function ownSessionsDir() {
|
|
86
|
+
const pinned = process.env.KISO_SESSIONS_DIR;
|
|
87
|
+
if (pinned !== undefined && pinned !== "")
|
|
88
|
+
return pinned;
|
|
89
|
+
const home = kisoHome();
|
|
90
|
+
if (!projectLayoutActive(home))
|
|
91
|
+
return join(home, LEGACY_SESSIONS_DIR);
|
|
92
|
+
const workspace = projectRoot();
|
|
93
|
+
const key = `${home}\0${workspace}`;
|
|
94
|
+
if (projectSessions?.key !== key)
|
|
95
|
+
projectSessions = { key, dir: projectDirFor(home, workspace) };
|
|
96
|
+
return projectSessions.dir;
|
|
97
|
+
}
|
|
98
|
+
let openFolder = null;
|
|
99
|
+
/** 0.40.0 (the lead's ruling) — a session from `_unknown`, or placed in
|
|
100
|
+
* another project's folder only by inference, resumes WHERE IT IS: a
|
|
101
|
+
* resume never moves a session. The store is then built on that folder,
|
|
102
|
+
* and null returns this process to its own. */
|
|
103
|
+
export function setOpenSessionFolder(dir) {
|
|
104
|
+
openFolder = dir;
|
|
105
|
+
}
|
|
106
|
+
/** The folder of the session this process has open: its own project's,
|
|
107
|
+
* or the one an unknown or inferred session was resumed in. */
|
|
43
108
|
export function sessionsDir() {
|
|
44
|
-
return
|
|
109
|
+
return openFolder ?? ownSessionsDir();
|
|
45
110
|
}
|
|
46
111
|
/** E1: the extension scan directory — KISO_EXTENSIONS_DIR overrides. */
|
|
47
112
|
export function extensionsDir() {
|
|
@@ -254,6 +319,17 @@ export let mergedConfig = {};
|
|
|
254
319
|
export function setMergedConfig(value) {
|
|
255
320
|
mergedConfig = value;
|
|
256
321
|
}
|
|
322
|
+
export let retryShown = null;
|
|
323
|
+
export function setRetryShown(value) {
|
|
324
|
+
retryShown = value;
|
|
325
|
+
}
|
|
326
|
+
/** The pending retry as a row shows it, with the time left computed NOW:
|
|
327
|
+
* the rows repaint on their own timers, so the countdown moves between
|
|
328
|
+
* the kernel's announcements. */
|
|
329
|
+
export function retryOnRow() {
|
|
330
|
+
const r = retryShown;
|
|
331
|
+
return r === null ? null : { attempt: r.attempt, maxRetries: r.maxRetries, code: r.code, remainingMs: r.until - Date.now() };
|
|
332
|
+
}
|
|
257
333
|
/** merge round B: the resolved context window (env > config.contextWindow) —
|
|
258
334
|
* chat.ts's contextWindowTokens() consults it before the env. */
|
|
259
335
|
export let configuredWindow;
|
|
@@ -293,6 +369,18 @@ export function setCurrentModelName(value) {
|
|
|
293
369
|
currentModelName = value;
|
|
294
370
|
}
|
|
295
371
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
|
372
|
+
/** 0.40.0: whether the catastrophe floor is on (floor.ts) — the user
|
|
373
|
+
* config's `floor`, read where the chain is assembled. */
|
|
374
|
+
export let floorOn = true;
|
|
375
|
+
export function setFloorOn(value) {
|
|
376
|
+
floorOn = value;
|
|
377
|
+
}
|
|
378
|
+
/** 0.40.0: the calls a saved allow never carries (protected-writes.ts) —
|
|
379
|
+
* set where the chain is assembled, read where a first grant joins it. */
|
|
380
|
+
export let neverInherited = () => false;
|
|
381
|
+
export function setNeverInherited(value) {
|
|
382
|
+
neverInherited = value;
|
|
383
|
+
}
|
|
296
384
|
export let loadedExtensions = [];
|
|
297
385
|
/** R-D 0.1.45: the BUILT-IN layer — the three default official extensions,
|
|
298
386
|
* shipped with the cli (module imports, never a disk scan; builtin.ts).
|
|
@@ -311,6 +399,14 @@ export function setExtensionLists(builtIn, user, project, loaded) {
|
|
|
311
399
|
projectExtensions = project;
|
|
312
400
|
loadedExtensions = loaded;
|
|
313
401
|
}
|
|
402
|
+
/** 0.40.0: the skills catalog of the loaded skills extension — the scan the
|
|
403
|
+
* model's index came from, so `/skill` and `/skills` cannot list a
|
|
404
|
+
* different set. Null when no skills extension is loaded (or a user
|
|
405
|
+
* extension named "skills" shadows it and reports none). */
|
|
406
|
+
export function loadedSkillsCatalog() {
|
|
407
|
+
const ext = loadedExtensions.find((e) => e.name === "skills");
|
|
408
|
+
return ext?.catalog ?? null;
|
|
409
|
+
}
|
|
314
410
|
/** W21: the CURRENT agent's extensions array — set by makeAgent, the
|
|
315
411
|
* don't-ask-again writer pushes the generated extension into it so a
|
|
316
412
|
* first-time rule joins the chain at the NEXT run (the run's policies
|
package/dist/trust-ui.js
CHANGED
|
@@ -11,7 +11,8 @@ import { join } from "node:path";
|
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
import { askDeclineAll, askView, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, unansweredAskView } from "@vincemakes/kiso-tui";
|
|
13
13
|
import { projectArtifacts, recordTrust, trustFor } from "@vincemakes/kiso-runtime";
|
|
14
|
-
import { bodyLog, currentAgentExtensions, dock, extensionsDir, kisoHome, mergedTempPaths } from "./state.js";
|
|
14
|
+
import { bodyLog, currentAgentExtensions, dock, extensionsDir, kisoHome, mergedTempPaths, neverInherited } from "./state.js";
|
|
15
|
+
import { guardSavedAllow } from "./protected-writes.js";
|
|
15
16
|
import { loadUserConfig, resolveProjectTrustPolicy } from "./config.js";
|
|
16
17
|
import { getMode } from "./mode.js";
|
|
17
18
|
/**
|
|
@@ -145,6 +146,13 @@ export function parseFallbackAnswer(answer) {
|
|
|
145
146
|
export function askUi(input) {
|
|
146
147
|
return {
|
|
147
148
|
ask: async (spec) => {
|
|
149
|
+
// Launch-weekend plan §2: dontAsk asks nobody anything — a
|
|
150
|
+
// question the model puts is declined, recorded as unanswered,
|
|
151
|
+
// exactly as a non-interactive session declines it.
|
|
152
|
+
if (getMode() === "dontAsk") {
|
|
153
|
+
bodyLog("[dontAsk] the model's question was declined — nothing asks in dontAsk");
|
|
154
|
+
return askDeclineAll(spec);
|
|
155
|
+
}
|
|
148
156
|
const verdict = await askPanel(input, askView(spec));
|
|
149
157
|
return verdict.action === "answers" ? verdict.result : askDeclineAll(spec);
|
|
150
158
|
},
|
|
@@ -236,10 +244,13 @@ export async function addDontAskAgainRule(rule) {
|
|
|
236
244
|
const mod = (await import(`${pathToFileURL(file).href}?grant=${(grantSerial += 1)}`));
|
|
237
245
|
if (mod.default === undefined)
|
|
238
246
|
return;
|
|
247
|
+
// 0.40.0: wrapped as at startup — a first grant must not carry what a
|
|
248
|
+
// saved allow never carries.
|
|
249
|
+
const joined = guardSavedAllow(mod.default, neverInherited);
|
|
239
250
|
if (at >= 0)
|
|
240
|
-
currentAgentExtensions[at] =
|
|
251
|
+
currentAgentExtensions[at] = joined;
|
|
241
252
|
else
|
|
242
|
-
currentAgentExtensions.push(
|
|
253
|
+
currentAgentExtensions.push(joined);
|
|
243
254
|
}
|
|
244
255
|
/** RL-F5 — the rules a generated file already holds. The `new Set([...])`
|
|
245
256
|
* literal is JSON, which is what makes reading it back honest rather than
|
|
@@ -307,8 +318,11 @@ export async function resolveProjectTrust(input) {
|
|
|
307
318
|
return null; // refused is sticky — no re-ask
|
|
308
319
|
// First discovery — list every artifact (file name + digest short
|
|
309
320
|
// prefix) and ask the human ONCE.
|
|
310
|
-
|
|
311
|
-
|
|
321
|
+
// The lead's review of #63, B5: dontAsk takes this same path — nothing
|
|
322
|
+
// asks, so the project is not loaded, the note says so, and no refusal
|
|
323
|
+
// is recorded (a later asking session can still decide).
|
|
324
|
+
if (!process.stdin.isTTY || getMode() === "dontAsk") {
|
|
325
|
+
console.error(`${getMode() === "dontAsk" ? "[dontAsk] " : ""}${projectUntrustedNote(artifacts.files.length, artifacts.root)}`);
|
|
312
326
|
return null;
|
|
313
327
|
}
|
|
314
328
|
// v2c: the shared input (the editor on a TTY) reads the answer.
|
|
@@ -457,6 +471,16 @@ function readdirSyncSafe(dir) {
|
|
|
457
471
|
* fabricated, round 10). The resolution feedback lands in the body —
|
|
458
472
|
* the body is the single stdout writer, never a stray console.log. */
|
|
459
473
|
export async function resolveUncertains(session, input, isCancelled) {
|
|
474
|
+
// The lead's review of #63, B5: under dontAsk nothing asks, so the
|
|
475
|
+
// uncertain executions are left exactly as a cancel leaves them —
|
|
476
|
+
// uncertain and durable, no rerun or abandon fabricated — and the one
|
|
477
|
+
// line says what to do. An unattended session must not sit on a panel.
|
|
478
|
+
if (getMode() === "dontAsk") {
|
|
479
|
+
const n = session.uncertainExecutions().length;
|
|
480
|
+
if (n > 0)
|
|
481
|
+
bodyLog(`[dontAsk] ${n} uncertain execution${n === 1 ? "" : "s"} left unresolved — resolve them in an asking mode`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
460
484
|
for (const uncertain of session.uncertainExecutions()) {
|
|
461
485
|
// KC3.5 §4: an interrupted ask_user is not a side effect that may
|
|
462
486
|
// have applied — it is a question nobody answered. The COPY says
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "kiso-code \u2014 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",
|
|
@@ -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.40.0",
|
|
22
|
+
"@vincemakes/kiso-core": "0.40.0",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.40.0",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.40.0",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.40.0",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.40.0",
|
|
27
|
+
"@vincemakes/kiso-provider-openai-responses": "0.40.0",
|
|
28
|
+
"@vincemakes/kiso-runtime": "0.40.0",
|
|
29
|
+
"@vincemakes/kiso-skills-ext": "0.40.0",
|
|
30
|
+
"@vincemakes/kiso-subagent-ext": "0.40.0",
|
|
31
|
+
"@vincemakes/kiso-task-ext": "0.40.0",
|
|
32
|
+
"@vincemakes/kiso-tools-node": "0.40.0",
|
|
33
|
+
"@vincemakes/kiso-tui": "0.40.0",
|
|
34
|
+
"@vincemakes/kiso-tui-cells": "0.40.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^26.1.2",
|