@vincemakes/kiso-code 0.45.2 → 0.46.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 +2 -1
- package/dist/builtin.d.ts +5 -4
- package/dist/builtin.js +12 -7
- package/dist/chat.js +121 -19
- package/dist/child-result.d.ts +23 -0
- package/dist/child-result.js +59 -0
- package/dist/config.d.ts +13 -6
- package/dist/config.js +30 -16
- package/dist/create-coding-agent.js +3 -1
- package/dist/dispatch.d.ts +2 -0
- package/dist/dispatch.js +163 -31
- package/dist/empty-sessions.d.ts +4 -2
- package/dist/empty-sessions.js +6 -2
- package/dist/external-editor.js +4 -1
- package/dist/floor.js +71 -26
- package/dist/index.js +89 -29
- package/dist/launch.d.ts +29 -0
- package/dist/launch.js +43 -0
- package/dist/mode.d.ts +90 -17
- package/dist/mode.js +135 -32
- package/dist/protected-shell.js +60 -19
- package/dist/protected-writes.d.ts +2 -2
- package/dist/protected-writes.js +2 -2
- package/dist/readonly-shell.js +16 -14
- package/dist/replay.js +3 -0
- package/dist/resume.js +2 -2
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +41 -11
- package/dist/shell-words.d.ts +34 -1
- package/dist/shell-words.js +84 -4
- package/dist/state.d.ts +31 -0
- package/dist/state.js +88 -2
- package/dist/task-notice.d.ts +22 -0
- package/dist/task-notice.js +86 -0
- package/dist/trust-ui.js +6 -6
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -37,14 +37,16 @@ import { skillMenuItems } from "./skill-invoke.js";
|
|
|
37
37
|
import { canonicalPath, hasSession, locateSession, projectLayoutActive, sessionFolders } from "./projects.js";
|
|
38
38
|
import { reverseMigration } from "./session-migration.js";
|
|
39
39
|
import { defaultTrashRoot, findEmptySessions, moveToTrash } from "./empty-sessions.js";
|
|
40
|
+
import { browserCommand, launchCommand } from "./launch.js";
|
|
40
41
|
import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
41
42
|
import { isProtectedPath, protectedIdentity, PROTECTED_REFUSAL } from "@vincemakes/kiso-tools-node";
|
|
42
|
-
import {
|
|
43
|
+
import { OFFERED_MODES, applyModeState, envModeLayer, getDontAsk, modeDisplay, parseMode, resolveModeLayers } from "./mode.js";
|
|
43
44
|
import { activeStoreDir, setActiveStoreDir, agentModel, atFiles, body, bodyLog, kisoHome, workspaceRoot, projectRoot, ownSessionsDir, setOpenSessionFolder, builtInExtensions, currentFaux, dock, loadedExtensions, mergedConfig, mergedTempPaths, modelChoice, projectExtensions, configModels, configuredWindow, agentBaseUrl, currentModelName, currentAgentExtensions, sessionStoreRef, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setCurrentProfileName, setExtensionLists, protectedFiles, setMergedConfig, setModelChoice, setSessionStore, userExtensions, VERSION, lastBinding, acceptDrift, setAcceptDrift, loadedSkillsCatalog, queuedSwitchLines } from "./state.js";
|
|
44
45
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
45
46
|
import { chat, contextWindowTokens, displayCtxRatio, microcompactThresholdFor, statusModelLabel } from "./chat.js";
|
|
46
47
|
import { preferences, usePreferences } from "./preferences.js";
|
|
47
|
-
import { settingsLayers } from "./state.js";
|
|
48
|
+
import { settingsLayers, setChildTurnBudget, stopAllTasks } from "./state.js";
|
|
49
|
+
import { finishChild } from "./child-result.js";
|
|
48
50
|
import { loadUserConfig, resolveAutoCompact } from "./config.js";
|
|
49
51
|
import { checkForUpdate, knownUpdate, updateCardLines } from "./update-check.js";
|
|
50
52
|
import { tmuxMouseHint } from "./tmux-hint.js";
|
|
@@ -74,14 +76,14 @@ export { applyProjectMerges } from "./trust-ui.js";
|
|
|
74
76
|
* path's exact semantics (auto-denied asks, no chips, no pops). */
|
|
75
77
|
/** OR-4: hand a URL to the platform's opener, detached, and never let a
|
|
76
78
|
* missing or failing opener touch the sign-in — the printed URL is the
|
|
77
|
-
* path that always works. macOS `open`, Linux `xdg-open
|
|
78
|
-
* URL
|
|
79
|
+
* path that always works. macOS `open`, Linux `xdg-open`, Windows
|
|
80
|
+
* rundll32's URL handler (launch.ts); elsewhere the URL stays printed. */
|
|
79
81
|
function openInBrowser(url) {
|
|
80
|
-
const opener =
|
|
82
|
+
const opener = browserCommand(url);
|
|
81
83
|
if (opener === null)
|
|
82
84
|
return;
|
|
83
85
|
try {
|
|
84
|
-
spawn(opener, [
|
|
86
|
+
spawn(opener[0], [...opener[1]], { detached: true, stdio: "ignore" }).unref();
|
|
85
87
|
}
|
|
86
88
|
catch {
|
|
87
89
|
// the URL is on screen; opening it is the person's fallback
|
|
@@ -180,6 +182,9 @@ function readlineInput(rl) {
|
|
|
180
182
|
/* §2.3: same as ctrl+o above — readline has no binding, and a
|
|
181
183
|
* dock-less session has no committed rows to reprint. */
|
|
182
184
|
},
|
|
185
|
+
onBackground() {
|
|
186
|
+
/* 3e: readline has no ctrl+b — /tasks and a steer still work. */
|
|
187
|
+
},
|
|
183
188
|
onEditor() {
|
|
184
189
|
/* §2.4: readline has no ctrl+g, and a piped session has no
|
|
185
190
|
* composer to hand over. */
|
|
@@ -274,6 +279,9 @@ function editorInput(editor) {
|
|
|
274
279
|
onThink(cb) {
|
|
275
280
|
editor.onThink(cb);
|
|
276
281
|
},
|
|
282
|
+
onBackground(cb) {
|
|
283
|
+
editor.onBackground(cb);
|
|
284
|
+
},
|
|
277
285
|
onEditor(cb) {
|
|
278
286
|
editor.onEditor(cb);
|
|
279
287
|
},
|
|
@@ -488,10 +496,10 @@ function bannerExtensionText() {
|
|
|
488
496
|
// terminal layer (extensionsBannerText — a pure function of the three
|
|
489
497
|
// name lists, including the "(connecting…)" in-flight label). Which
|
|
490
498
|
// lists exist is the CLI's fact and stays here.
|
|
491
|
-
// 0.40.0:
|
|
492
|
-
// (builtin.ts offInDontAsk) — the banner says so
|
|
493
|
-
//
|
|
494
|
-
const builtIn = builtInExtensions.map((e) => (e.name === "ask" &&
|
|
499
|
+
// 0.40.0: with the don't-ask switch on the ask extension is loaded but
|
|
500
|
+
// offers no tool (builtin.ts offInDontAsk) — the banner says so, rather
|
|
501
|
+
// than listing it as if it could ask.
|
|
502
|
+
const builtIn = builtInExtensions.map((e) => (e.name === "ask" && getDontAsk() ? { name: e.name, note: "off in dontAsk" } : e));
|
|
495
503
|
return extensionsBannerText(builtIn, userExtensions, projectExtensions);
|
|
496
504
|
}
|
|
497
505
|
/** E1: the startup banner line(s) — TTY: logo + merged extensions + the
|
|
@@ -569,7 +577,7 @@ function extensionsBanner(resume = []) {
|
|
|
569
577
|
};
|
|
570
578
|
body.banner(VERSION, text.replace(/^ · /, ""), resume, {
|
|
571
579
|
model: agentModel,
|
|
572
|
-
mode:
|
|
580
|
+
mode: modeDisplay(),
|
|
573
581
|
cwd: cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd,
|
|
574
582
|
// DC-49 — REALPATH on both sides. A symlinked HOME (or a symlinked
|
|
575
583
|
// cwd) compares unequal as raw strings while being the same
|
|
@@ -808,7 +816,7 @@ function paintBootStatus(session) {
|
|
|
808
816
|
return;
|
|
809
817
|
// DF-0330-F1: the BOOT row gets the budget too — it is the same row, and
|
|
810
818
|
// the opening screen is where a long model id is first seen.
|
|
811
|
-
dock.setStatus(idleStatus(
|
|
819
|
+
dock.setStatus(idleStatus(modeDisplay(), statusModelLabel(session), displayCtxRatio(session), undefined, process.stdout.columns > 0 ? process.stdout.columns : 80));
|
|
812
820
|
}
|
|
813
821
|
/** PH-1a (finding PH-F12): a usage error raised from inside the TUI —
|
|
814
822
|
* main's finally still runs (dock teardown, agent close, temp cleanup)
|
|
@@ -816,6 +824,9 @@ function paintBootStatus(session) {
|
|
|
816
824
|
class CliUsageError extends Error {
|
|
817
825
|
exitCode = 2;
|
|
818
826
|
}
|
|
827
|
+
/** ADR-0058 3d: set when this process is a background child (its budget
|
|
828
|
+
* and where its answer goes) — finished once its turn is done. */
|
|
829
|
+
let childEnd = null;
|
|
819
830
|
/** The /resume+/clear mini-spec — the chat LOOP: chat() ends with a
|
|
820
831
|
* directive; a switch re-enters it on another session with the SAME
|
|
821
832
|
* editor. First entry paints the banner; a switch paints one notice
|
|
@@ -1103,8 +1114,11 @@ async function chatLoop(first, firstId, input, autoCompact) {
|
|
|
1103
1114
|
...(process.stdin.isTTY ? { pick: () => pickSession(agent, input) } : {}),
|
|
1104
1115
|
};
|
|
1105
1116
|
const end = await chat(session, currentFaux, input, autoCompact, nav, seed);
|
|
1106
|
-
if (end.next === "exit")
|
|
1117
|
+
if (end.next === "exit") {
|
|
1118
|
+
if (childEnd !== null)
|
|
1119
|
+
await finishChild(session, childEnd);
|
|
1107
1120
|
return;
|
|
1121
|
+
}
|
|
1108
1122
|
// DC-57: the lines that arrived with the switch command ride INTO the
|
|
1109
1123
|
// next entry — they were aimed at the session being asked for.
|
|
1110
1124
|
seed = end.lines ?? [];
|
|
@@ -1250,23 +1264,30 @@ async function main() {
|
|
|
1250
1264
|
}
|
|
1251
1265
|
args.splice(modelArgIdx, 2);
|
|
1252
1266
|
}
|
|
1253
|
-
// Modes:
|
|
1254
|
-
//
|
|
1255
|
-
//
|
|
1267
|
+
// Modes: flag > env > project config > user config > default, for the
|
|
1268
|
+
// tier and the don't-ask switch each (mode.ts resolveModeLayers). The
|
|
1269
|
+
// project config exists only once the trust gate has passed, so it joins
|
|
1270
|
+
// later (applyConfigMode); until then the other three decide.
|
|
1271
|
+
const flagLayer = {};
|
|
1256
1272
|
const modeFlag = args.indexOf("--mode");
|
|
1257
1273
|
if (modeFlag !== -1) {
|
|
1258
|
-
const
|
|
1259
|
-
if (
|
|
1260
|
-
console.error(`unknown mode: ${
|
|
1274
|
+
const raw = args[modeFlag + 1];
|
|
1275
|
+
if (raw === undefined || parseMode(raw) === undefined) {
|
|
1276
|
+
console.error(`unknown mode: ${raw} (tiers: ${OFFERED_MODES.join(", ")})`);
|
|
1261
1277
|
process.exit(2);
|
|
1262
1278
|
}
|
|
1263
|
-
|
|
1264
|
-
settingsLayers.modeFlag =
|
|
1279
|
+
flagLayer.mode = raw;
|
|
1280
|
+
settingsLayers.modeFlag = raw;
|
|
1265
1281
|
args.splice(modeFlag, 2);
|
|
1266
1282
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1283
|
+
const dontAskFlag = args.indexOf("--dont-ask");
|
|
1284
|
+
if (dontAskFlag !== -1) {
|
|
1285
|
+
flagLayer.dontAsk = true;
|
|
1286
|
+
settingsLayers.dontAskFlag = true;
|
|
1287
|
+
args.splice(dontAskFlag, 1);
|
|
1269
1288
|
}
|
|
1289
|
+
const modeLayers = (project) => ({ flag: flagLayer, env: envModeLayer(), project, user: loadUserConfig() });
|
|
1290
|
+
applyModeState(resolveModeLayers(modeLayers(null)));
|
|
1270
1291
|
// CX-1 F5: --task-file <path> — the file is the ONE user turn (the
|
|
1271
1292
|
// structured child entry). Read BEFORE any session exists: an
|
|
1272
1293
|
// unreadable file is a usage error with nothing executed.
|
|
@@ -1291,6 +1312,35 @@ async function main() {
|
|
|
1291
1312
|
args.splice(i, 2);
|
|
1292
1313
|
}
|
|
1293
1314
|
}
|
|
1315
|
+
// ADR-0058 3d: a background child's --max-turns N (its budget, D6) and
|
|
1316
|
+
// --result-file <path> (where its answer goes). Child-only: without
|
|
1317
|
+
// --task-file they are refused — the interactive door has no turn
|
|
1318
|
+
// limit (R3e).
|
|
1319
|
+
{
|
|
1320
|
+
const end = {};
|
|
1321
|
+
for (const flag of ["--max-turns", "--result-file"]) {
|
|
1322
|
+
const i = args.indexOf(flag);
|
|
1323
|
+
if (i === -1)
|
|
1324
|
+
continue;
|
|
1325
|
+
const value = args[i + 1];
|
|
1326
|
+
if (taskFile === undefined)
|
|
1327
|
+
throw new CliUsageError(`${flag} needs --task-file — it is for a delegated child only`);
|
|
1328
|
+
if (value === undefined || value.startsWith("--"))
|
|
1329
|
+
throw new CliUsageError(`${flag} needs a value`);
|
|
1330
|
+
if (flag === "--max-turns") {
|
|
1331
|
+
const n = Number(value);
|
|
1332
|
+
if (!Number.isInteger(n) || n < 1)
|
|
1333
|
+
throw new CliUsageError(`--max-turns: expected a whole number of at least 1, got ${value}`);
|
|
1334
|
+
end.maxTurns = n;
|
|
1335
|
+
}
|
|
1336
|
+
else
|
|
1337
|
+
end.resultFile = value;
|
|
1338
|
+
args.splice(i, 2);
|
|
1339
|
+
}
|
|
1340
|
+
if (end.maxTurns !== undefined || end.resultFile !== undefined)
|
|
1341
|
+
childEnd = end;
|
|
1342
|
+
setChildTurnBudget(end.maxTurns);
|
|
1343
|
+
}
|
|
1294
1344
|
// 0.40.0: `kiso sessions --all | --current` — the one command that takes
|
|
1295
1345
|
// them. Parsed only there, so neither ever becomes a session id. Absent
|
|
1296
1346
|
// = each form's default (TTY: current; pipe: all, today's bytes).
|
|
@@ -1433,10 +1483,9 @@ async function main() {
|
|
|
1433
1483
|
try {
|
|
1434
1484
|
// merge round B: the project config's mode applies AFTER the trust gate
|
|
1435
1485
|
// (its verdict decides whether the project config exists at all) —
|
|
1436
|
-
//
|
|
1486
|
+
// under whatever a higher layer (the flags, the env) already decided.
|
|
1437
1487
|
const applyConfigMode = () => {
|
|
1438
|
-
|
|
1439
|
-
setMode(mergedConfig.mode);
|
|
1488
|
+
applyModeState(resolveModeLayers(modeLayers(settingsLayers.project)));
|
|
1440
1489
|
};
|
|
1441
1490
|
if (printPrompt !== undefined) {
|
|
1442
1491
|
// the -p flow: recovery-first one-shot, the resume() machinery
|
|
@@ -1553,7 +1602,7 @@ async function main() {
|
|
|
1553
1602
|
console.log("kiso sessions --prune-empty --yes moves them to the Trash");
|
|
1554
1603
|
}
|
|
1555
1604
|
else
|
|
1556
|
-
console.log(`moved ${n(empty.length, "empty session")} to ${moveToTrash(empty, defaultTrashRoot())}`);
|
|
1605
|
+
console.log(`moved ${n(empty.length, "empty session")} to ${moveToTrash(empty, defaultTrashRoot(kisoHome()))}`);
|
|
1557
1606
|
if (inUse > 0)
|
|
1558
1607
|
console.log(`skipped ${n(inUse, "empty session")} in use — a live process holds the lock`);
|
|
1559
1608
|
break;
|
|
@@ -1719,7 +1768,9 @@ async function main() {
|
|
|
1719
1768
|
// npm's to explain; the message names the manual command so
|
|
1720
1769
|
// the person can run it with whatever their setup needs.
|
|
1721
1770
|
const [bin, ...installArgs] = INSTALL_COMMAND;
|
|
1722
|
-
|
|
1771
|
+
// Windows: npm is npm.cmd — through cmd.exe (launch.ts)
|
|
1772
|
+
const l = launchCommand(bin, installArgs);
|
|
1773
|
+
const r = spawnSync(l.file, [...l.args], { stdio: "inherit", ...(l.windowsVerbatimArguments === true ? { windowsVerbatimArguments: true } : {}) });
|
|
1723
1774
|
if (r.error !== undefined || r.status !== 0) {
|
|
1724
1775
|
const why = r.error !== undefined ? `could not run npm (${r.error.message})` : `npm exited ${r.status ?? "on a signal"}`;
|
|
1725
1776
|
process.stderr.write(`kiso update: ${why} — run it yourself: ${INSTALL_COMMAND.join(" ")}\n`);
|
|
@@ -1751,7 +1802,8 @@ async function main() {
|
|
|
1751
1802
|
" kiso help this help\n\n" +
|
|
1752
1803
|
"flags (any position):\n" +
|
|
1753
1804
|
" --model <profile|provider/model> pick the model (also /model in-session)\n" +
|
|
1754
|
-
" --mode <tier> approval tier: default|accept-edits|plan|
|
|
1805
|
+
" --mode <tier> approval tier: default|accept-edits|plan|full-access\n" +
|
|
1806
|
+
" --dont-ask never stop for a person: what would ask is refused\n" +
|
|
1755
1807
|
" --version print the version\n\n" +
|
|
1756
1808
|
"configuration:\n" +
|
|
1757
1809
|
" no key keyless faux demo (a scripted four-round session)\n" +
|
|
@@ -1795,11 +1847,19 @@ async function main() {
|
|
|
1795
1847
|
// variable now). A signal death skips this and leaves the dead-pid
|
|
1796
1848
|
// residue — the dead-holder takeover recovers it by design
|
|
1797
1849
|
// (ADR-0050); the lock never outlives a live writer either way.
|
|
1850
|
+
// ADR-0058: a clean exit stops the session's tasks first — or, when
|
|
1851
|
+
// the person chose to leave them (3e), only the moved commands. What
|
|
1852
|
+
// could not be confirmed is said, never called stopped.
|
|
1853
|
+
const tasksAtExit = await stopAllTasks();
|
|
1798
1854
|
agent?.close();
|
|
1799
1855
|
// v2b: the dock tears down on EVERY exit path — CSI r resets the
|
|
1800
1856
|
// scroll region, the cursor lands at the input line, no broken
|
|
1801
1857
|
// terminal (kill -9 excepted; `reset` saves it).
|
|
1802
1858
|
dock.exit();
|
|
1859
|
+
for (const id of tasksAtExit.unconfirmed)
|
|
1860
|
+
console.error(`${id}: stop unconfirmed — outcome unknown`);
|
|
1861
|
+
if (tasksAtExit.left.length > 0)
|
|
1862
|
+
console.error(`left running: ${tasksAtExit.left.join(", ")} — reopen this session to hear how ${tasksAtExit.left.length === 1 ? "it ends" : "they end"}`);
|
|
1803
1863
|
// finding #8 (P1): extension dispose runs on the same exit path — a
|
|
1804
1864
|
// dispose failure prints one line and NEVER changes the exit code.
|
|
1805
1865
|
await disposeExtensions(loadedExtensions);
|
package/dist/launch.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows P3 — how the CLI's small spawns start: `kiso update` (npm), the
|
|
3
|
+
* external editor, and the sign-in page.
|
|
4
|
+
*
|
|
5
|
+
* npm is npm.cmd and an editor is often code.cmd: a .cmd/.bat shim needs
|
|
6
|
+
* cmd.exe to start, and Node will not start one without a shell. Rather
|
|
7
|
+
* than `shell: true`, the call goes to cmd.exe itself — `/d /s /c "<line>"`
|
|
8
|
+
* — with every word quoted by `viaCmd`, the one function here (the
|
|
9
|
+
* cross-spawn rules: CommandLineToArgvW quoting, then a caret before each
|
|
10
|
+
* cmd.exe metacharacter). The shell tool never comes this way: it only
|
|
11
|
+
* ever starts bash (P1).
|
|
12
|
+
*
|
|
13
|
+
* Pure — no process is started here, so importing it moves no test into
|
|
14
|
+
* the pty pool.
|
|
15
|
+
*/
|
|
16
|
+
export interface Launch {
|
|
17
|
+
readonly file: string;
|
|
18
|
+
readonly args: readonly string[];
|
|
19
|
+
/** Windows: the line is cmd.exe's to parse, not Node's to requote */
|
|
20
|
+
readonly windowsVerbatimArguments?: true;
|
|
21
|
+
}
|
|
22
|
+
/** `bin args` as cmd.exe starts it. */
|
|
23
|
+
export declare function viaCmd(bin: string, args: readonly string[], env?: NodeJS.ProcessEnv): Launch;
|
|
24
|
+
/** `bin args` as this platform starts it: directly, or through cmd.exe on Windows. */
|
|
25
|
+
export declare function launchCommand(bin: string, args: readonly string[]): Launch;
|
|
26
|
+
/** The platform's URL opener — `rundll32`'s URL handler on Windows (cmd's
|
|
27
|
+
* `start` would cut a sign-in URL at its first `&`) — or null, and the
|
|
28
|
+
* URL stays printed. */
|
|
29
|
+
export declare function browserCommand(url: string): readonly [string, readonly string[]] | null;
|
package/dist/launch.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows P3 — how the CLI's small spawns start: `kiso update` (npm), the
|
|
3
|
+
* external editor, and the sign-in page.
|
|
4
|
+
*
|
|
5
|
+
* npm is npm.cmd and an editor is often code.cmd: a .cmd/.bat shim needs
|
|
6
|
+
* cmd.exe to start, and Node will not start one without a shell. Rather
|
|
7
|
+
* than `shell: true`, the call goes to cmd.exe itself — `/d /s /c "<line>"`
|
|
8
|
+
* — with every word quoted by `viaCmd`, the one function here (the
|
|
9
|
+
* cross-spawn rules: CommandLineToArgvW quoting, then a caret before each
|
|
10
|
+
* cmd.exe metacharacter). The shell tool never comes this way: it only
|
|
11
|
+
* ever starts bash (P1).
|
|
12
|
+
*
|
|
13
|
+
* Pure — no process is started here, so importing it moves no test into
|
|
14
|
+
* the pty pool.
|
|
15
|
+
*/
|
|
16
|
+
/** cmd.exe's metacharacters: each is escaped with a caret. */
|
|
17
|
+
const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
18
|
+
/** One argument, quoted as CommandLineToArgvW reads it, then escaped for cmd.exe. */
|
|
19
|
+
function cmdArgument(arg) {
|
|
20
|
+
const quoted = `"${arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
21
|
+
return quoted.replace(CMD_META, "^$1");
|
|
22
|
+
}
|
|
23
|
+
/** `bin args` as cmd.exe starts it. */
|
|
24
|
+
export function viaCmd(bin, args, env = process.env) {
|
|
25
|
+
const line = [bin.replace(CMD_META, "^$1"), ...args.map(cmdArgument)].join(" ");
|
|
26
|
+
return { file: env.ComSpec ?? "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], windowsVerbatimArguments: true };
|
|
27
|
+
}
|
|
28
|
+
/** `bin args` as this platform starts it: directly, or through cmd.exe on Windows. */
|
|
29
|
+
export function launchCommand(bin, args) {
|
|
30
|
+
return process.platform === "win32" ? viaCmd(bin, args) : { file: bin, args };
|
|
31
|
+
}
|
|
32
|
+
/** The platform's URL opener — `rundll32`'s URL handler on Windows (cmd's
|
|
33
|
+
* `start` would cut a sign-in URL at its first `&`) — or null, and the
|
|
34
|
+
* URL stays printed. */
|
|
35
|
+
export function browserCommand(url) {
|
|
36
|
+
if (process.platform === "darwin")
|
|
37
|
+
return ["open", [url]];
|
|
38
|
+
if (process.platform === "linux")
|
|
39
|
+
return ["xdg-open", [url]];
|
|
40
|
+
if (process.platform === "win32")
|
|
41
|
+
return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
42
|
+
return null;
|
|
43
|
+
}
|
package/dist/mode.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Modes — the
|
|
2
|
+
* Modes — the built-in approval tiers, built ON the E1 policy chain
|
|
3
3
|
* (the kernel is untouched). Each tier is an in-process "mode:<name>"
|
|
4
4
|
* extension whose decide() is live — it only speaks when it is the
|
|
5
5
|
* CURRENT tier (otherwise abstain = no opinion, ADR-0042), so /mode
|
|
@@ -7,19 +7,37 @@
|
|
|
7
7
|
* field: an automated denial records decidedBy: "mode:<name>" — the
|
|
8
8
|
* audit sell. User-level extensions stay on the chain AFTER the mode
|
|
9
9
|
* tiers; a user deny always wins (the chain's deny>allow>ask
|
|
10
|
-
* monotonicity —
|
|
10
|
+
* monotonicity — full-access cannot override an extension deny).
|
|
11
|
+
*
|
|
12
|
+
* Two questions, two settings (the modes round, 2026-09-30). The TIER
|
|
13
|
+
* answers what the agent may do. The DON'T-ASK SWITCH answers whether
|
|
14
|
+
* kiso may stop for a person, and it composes with every tier: it never
|
|
15
|
+
* grants anything — what would ask is refused, a model's question is not
|
|
16
|
+
* offered, an uncertain execution stays uncertain. The old `dontAsk` tier
|
|
17
|
+
* was that switch welded to default's authority; it is still accepted,
|
|
18
|
+
* and means exactly that.
|
|
11
19
|
*/
|
|
12
20
|
import type { KisoExtension } from "@vincemakes/kiso-runtime";
|
|
13
|
-
export type Mode = "manual" | "default" | "accept-edits" | "plan" | "
|
|
14
|
-
/** Every tier kiso
|
|
15
|
-
* `/mode <name>`. */
|
|
21
|
+
export type Mode = "manual" | "default" | "accept-edits" | "plan" | "full-access";
|
|
22
|
+
/** Every tier kiso decides with. */
|
|
16
23
|
export declare const MODES: readonly Mode[];
|
|
17
|
-
/** The tiers kiso OFFERS a person
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* breaks. `dontAsk` joined: the unattended tier. */
|
|
24
|
+
/** The tiers kiso OFFERS a person: the picker, shift+tab, the printed
|
|
25
|
+
* list, the help. `manual` left the offer at 0.40.0 — a saved allow
|
|
26
|
+
* outranks its ask, so "asks for every tool" was never quite what it
|
|
27
|
+
* did — and stays accepted, so no config that names it breaks. */
|
|
22
28
|
export declare const OFFERED_MODES: readonly Mode[];
|
|
29
|
+
/** What a person reads for each tier — the status row, the picker, the
|
|
30
|
+
* notices. The typed value (config, --mode, KISO_MODE) keeps its hyphen. */
|
|
31
|
+
export declare const MODE_LABEL: Readonly<Record<Mode, string>>;
|
|
32
|
+
/** One tier plus, for the one spelling that carried it, the switch. */
|
|
33
|
+
export interface ModeSetting {
|
|
34
|
+
readonly mode: Mode;
|
|
35
|
+
readonly dontAsk?: true;
|
|
36
|
+
}
|
|
37
|
+
/** Every value a config, KISO_MODE, --mode or `/mode <name>` accepts. */
|
|
38
|
+
export declare const MODE_VALUES: readonly string[];
|
|
39
|
+
/** A typed tier: its value, its label ("full access"), or an old name. */
|
|
40
|
+
export declare function parseMode(raw: string): ModeSetting | undefined;
|
|
23
41
|
/** DC-36 — one line per tier, for the picker, TRANSCRIBED FROM decide()
|
|
24
42
|
* below rather than written fresh. A description that drifts from the
|
|
25
43
|
* behaviour is worse than none: this is the row a human reads before
|
|
@@ -34,21 +52,76 @@ export declare const OFFERED_MODES: readonly Mode[];
|
|
|
34
52
|
* is unchanged; the copy was the thing that was wrong. `plan` needs no
|
|
35
53
|
* qualification because it DENIES, and a deny is what nothing overrides. */
|
|
36
54
|
export declare const MODE_NOTE: Readonly<Record<Mode, string>>;
|
|
55
|
+
/** The switch in one line, for a surface that describes it, under the
|
|
56
|
+
* same 60-column rule. The /mode panel does not offer it: don't ask is
|
|
57
|
+
* not a tier, so it never sits beside them (owner, 2026-09-30) — the
|
|
58
|
+
* panel's header names it when it is on. */
|
|
59
|
+
export declare const DONT_ASK_NOTE = "never asks: what would ask is refused and the run goes on";
|
|
60
|
+
/** The switch. "old-name" is a switch that arrived bundled with the old
|
|
61
|
+
* `dontAsk` tier: it leaves with the tier, as leaving dontAsk always
|
|
62
|
+
* gave the asks back. A switch set on its own ("on") outlives every tier
|
|
63
|
+
* change. */
|
|
64
|
+
export type DontAskState = "off" | "on" | "old-name";
|
|
37
65
|
export declare function getMode(): Mode;
|
|
66
|
+
/** A tier switch. The switch stays as it is — unless it came with the
|
|
67
|
+
* old dontAsk tier, which this switch leaves. */
|
|
38
68
|
export declare function setMode(m: Mode): void;
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
69
|
+
/** A typed tier, old names included: `/mode dontAsk` is default with the
|
|
70
|
+
* switch on, exactly the tier it used to be. */
|
|
71
|
+
export declare function applyModeSetting(s: ModeSetting): void;
|
|
72
|
+
/** Whether kiso may stop for a person — every ask endpoint reads this. */
|
|
73
|
+
export declare function getDontAsk(): boolean;
|
|
74
|
+
export declare function setDontAsk(on: boolean): void;
|
|
75
|
+
/** The tier as the status row and the notices name it, with the switch
|
|
76
|
+
* beside it when it is on. */
|
|
77
|
+
export declare function modeDisplay(): string;
|
|
78
|
+
/** One layer's say: what a config file, the environment or the command
|
|
79
|
+
* line wrote. KisoConfig is one of these. */
|
|
80
|
+
export interface ModeLayer {
|
|
81
|
+
readonly mode?: string | undefined;
|
|
82
|
+
readonly dontAsk?: boolean | undefined;
|
|
83
|
+
}
|
|
84
|
+
export type ModeSource = "flag" | "env" | "project" | "user" | "default";
|
|
85
|
+
export interface ModeState {
|
|
86
|
+
readonly mode: Mode;
|
|
87
|
+
readonly dontAsk: DontAskState;
|
|
88
|
+
/** the layer each answer came from — /settings names it */
|
|
89
|
+
readonly from: {
|
|
90
|
+
readonly mode: ModeSource;
|
|
91
|
+
readonly dontAsk: ModeSource;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** KISO_MODE and KISO_DONT_ASK. A value kiso cannot read is no value —
|
|
95
|
+
* the next layer decides, as an unknown KISO_MODE always did. */
|
|
96
|
+
export declare function envModeLayer(env?: Readonly<Record<string, string | undefined>>): ModeLayer;
|
|
97
|
+
/**
|
|
98
|
+
* Precedence: flag > env > project config > user config > default, for
|
|
99
|
+
* each answer on its own. The tier comes from the highest layer that
|
|
100
|
+
* names a readable one. The switch comes from the highest layer that
|
|
101
|
+
* either sets it (`--dont-ask`, KISO_DONT_ASK, "dontAsk": true|false) or
|
|
102
|
+
* WON THE TIER with the old name `dontAsk` — an old name's switch counts
|
|
103
|
+
* only where that old name decided the tier. So every combination of old
|
|
104
|
+
* spellings resolves as it did when dontAsk was a tier: a user config
|
|
105
|
+
* saying "dontAsk" under `--mode bypass` still asks, because --mode won.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveModeLayers(layers: {
|
|
108
|
+
readonly flag?: ModeLayer | null | undefined;
|
|
109
|
+
readonly env?: ModeLayer | null | undefined;
|
|
110
|
+
readonly project?: ModeLayer | null | undefined;
|
|
111
|
+
readonly user?: ModeLayer | null | undefined;
|
|
112
|
+
}): ModeState;
|
|
113
|
+
/** Put a resolved state in force — startup, and again once the project
|
|
114
|
+
* config is known. */
|
|
115
|
+
export declare function applyModeState(s: ModeState): void;
|
|
116
|
+
/** The built-in mode tiers as chain extensions — named "mode:<tier>"
|
|
44
117
|
* so the runtime's decidedBy records exactly that (the runtime derives
|
|
45
118
|
* approvalPolicies from extensions[].approvals, tagging each with the
|
|
46
119
|
* extension name). The CURRENT tier is first: an all-allow chain records
|
|
47
120
|
* decidedBy = the FIRST SPEAKER, so an auto-allow under the startup mode
|
|
48
121
|
* names that mode honestly. Order never affects verdicts — the chain is
|
|
49
122
|
* deny>allow>ask over the SPEAKING verdicts (abstain = no opinion), so a
|
|
50
|
-
* user extension's deny wins over any mode tier,
|
|
51
|
-
* monotonicity e2e pins it). */
|
|
123
|
+
* user extension's deny wins over any mode tier, full-access included
|
|
124
|
+
* (the monotonicity e2e pins it). */
|
|
52
125
|
export declare function modeExtensions(workspaceRoot?: () => string): readonly KisoExtension[];
|
|
53
126
|
/** The plan tier's system prompt addition — injected at startup when the
|
|
54
127
|
* initial mode is plan (the session prompt is fixed at creation; runtime
|