@vincemakes/kiso-code 0.39.2 → 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 +34 -3
- package/dist/dispatch.d.ts +10 -2
- package/dist/dispatch.js +118 -20
- 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
package/dist/dispatch.js
CHANGED
|
@@ -3,17 +3,37 @@
|
|
|
3
3
|
* turns. The bodies moved verbatim from chat()'s closure; chat provides
|
|
4
4
|
* the context (the chain, the run state, the prompt arming).
|
|
5
5
|
*/
|
|
6
|
-
import { contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpRows, kUnit, modePickView, modelPickView, palette, renderEvent, settledLabel, slashCommandNames } from "@vincemakes/kiso-tui";
|
|
6
|
+
import { STATUS_GLYPHS, contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpRows, kUnit, modePickView, modelPickView, compactingStatus, palette, renderEvent, settledLabel, slashCommandNames } from "@vincemakes/kiso-tui";
|
|
7
7
|
import { newSessionId } from "./session-id.js";
|
|
8
8
|
import { buildAdapter, lookupModelMetadata, resolveContinuationScope, resolveReasoning } from "@vincemakes/kiso-runtime/internal";
|
|
9
|
-
import { MODES, MODE_NOTE, getMode, setMode } from "./mode.js";
|
|
9
|
+
import { MODES, MODE_NOTE, OFFERED_MODES, getMode, setMode } from "./mode.js";
|
|
10
10
|
import { clipboardWrite, lastAnswer } from "./clipboard.js";
|
|
11
|
-
import {
|
|
11
|
+
import { protectedBangReason, protectedShellVerdict } from "./protected-shell.js";
|
|
12
|
+
import { agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, setLastBinding } from "./state.js";
|
|
12
13
|
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
13
|
-
import { microcompactThresholdFor } from "./chat.js";
|
|
14
|
+
import { contextWindowTokens, microcompactThresholdFor, startStatusSpinner } from "./chat.js";
|
|
14
15
|
import { authForProfile, directWriteProfile, profileAvailable, resolveContextWindow, unavailableReason } from "./config.js";
|
|
15
16
|
import { shellTool } from "@vincemakes/kiso-tools-node";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { realpathSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { resolveSkillLine, skillsRows } from "./skill-invoke.js";
|
|
21
|
+
/** Where a skill directory really lives. Project and user skills are
|
|
22
|
+
* merged into one scan directory by symlink, so the scan root names a
|
|
23
|
+
* temp directory; the link's target is the answer a person can use. */
|
|
24
|
+
function skillSource(dir) {
|
|
25
|
+
const scan = process.env.KISO_SKILLS_DIR ?? join(kisoHome(), "skills");
|
|
26
|
+
try {
|
|
27
|
+
return tildePath(dirname(realpathSync(join(scan, dir))));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return tildePath(scan); // a broken link has no target to name
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function tildePath(p) {
|
|
34
|
+
const home = homedir();
|
|
35
|
+
return p === home || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
36
|
+
}
|
|
17
37
|
/** The picker's CLI half (owner 2026-09-08): a profile's LEGAL effort levels
|
|
18
38
|
* and its default, from the registry, shown wherever the profile is listed
|
|
19
39
|
* — kiso never silently drops a level, and now it shows the ones it takes.
|
|
@@ -130,9 +150,21 @@ export function parseBang(trimmed) {
|
|
|
130
150
|
* No approval panel, at any tier including plan: the human typed the
|
|
131
151
|
* command. That is not a policy exemption, it is structural — this
|
|
132
152
|
* dispatcher only ever sees lines a human submitted, so a model cannot
|
|
133
|
-
* reach this branch.
|
|
153
|
+
* reach this branch.
|
|
154
|
+
*
|
|
155
|
+
* One check it does take: kiso never serves its own credential store to a
|
|
156
|
+
* model, and `!cmd` hands its output to one. `!!cmd` does not — but the
|
|
157
|
+
* two are one keystroke apart, so a line naming a protected file runs
|
|
158
|
+
* under neither, and the store stays one `cat` away in the person's own
|
|
159
|
+
* terminal. */
|
|
134
160
|
function runBang(command, send, ctx) {
|
|
135
161
|
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
162
|
+
const guard = protectedShellVerdict(command, codingToolOptions().workspaceRoot, protectedFiles(), { home: homedir(), kisoHome: kisoHome() });
|
|
163
|
+
if (guard.refused) {
|
|
164
|
+
bodyLog(protectedBangReason(guard));
|
|
165
|
+
ctx.input.prompt();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
136
168
|
const controller = new AbortController();
|
|
137
169
|
activeBang = controller;
|
|
138
170
|
try {
|
|
@@ -469,7 +501,7 @@ export function dispatch(line, ctx) {
|
|
|
469
501
|
const picked = await new Promise((resolve) => {
|
|
470
502
|
ctx.input.panelAsk(modePickView({
|
|
471
503
|
header: `mode — current: ${current}`,
|
|
472
|
-
options:
|
|
504
|
+
options: OFFERED_MODES.map((name) => ({ label: name, note: [MODE_NOTE[name], ...(name === current ? ["current"] : [])].join(" · ") })),
|
|
473
505
|
},
|
|
474
506
|
// DC-12 (design §4): a panel WAITING ON A HUMAN says ❯.
|
|
475
507
|
ctx.isRunning() ? "❯ run paused" : `▸ ${current}`), (v) => resolve(v.action === "picked" ? v.result : null));
|
|
@@ -479,10 +511,10 @@ export function dispatch(line, ctx) {
|
|
|
479
511
|
ctx.input.prompt();
|
|
480
512
|
return; // esc — nothing switched, nothing said
|
|
481
513
|
}
|
|
482
|
-
const chosen = "index" in picked ?
|
|
514
|
+
const chosen = "index" in picked ? OFFERED_MODES[picked.index] : MODES.find((x) => x === picked.custom.trim());
|
|
483
515
|
if (chosen === undefined) {
|
|
484
516
|
bodyLog(`no such mode: ${"index" in picked ? String(picked.index) : picked.custom.trim()}`);
|
|
485
|
-
bodyLog(`tiers: ${
|
|
517
|
+
bodyLog(`tiers: ${OFFERED_MODES.join(" ")}`);
|
|
486
518
|
}
|
|
487
519
|
else {
|
|
488
520
|
setMode(chosen);
|
|
@@ -493,11 +525,11 @@ export function dispatch(line, ctx) {
|
|
|
493
525
|
return;
|
|
494
526
|
}
|
|
495
527
|
bodyLog(`mode ${getMode()}`);
|
|
496
|
-
bodyLog(`tiers: ${
|
|
528
|
+
bodyLog(`tiers: ${OFFERED_MODES.join(" ")}`);
|
|
497
529
|
}
|
|
498
530
|
else if (m === undefined) {
|
|
499
531
|
bodyLog(`no such mode: ${trimmed.slice(5).trim()}`);
|
|
500
|
-
bodyLog(`tiers: ${
|
|
532
|
+
bodyLog(`tiers: ${OFFERED_MODES.join(" ")}`);
|
|
501
533
|
}
|
|
502
534
|
else {
|
|
503
535
|
setMode(m);
|
|
@@ -666,11 +698,16 @@ export function dispatch(line, ctx) {
|
|
|
666
698
|
...(profile.baseUrl !== undefined ? { baseUrl: profile.baseUrl } : {}),
|
|
667
699
|
}),
|
|
668
700
|
},
|
|
701
|
+
// A1b: the in-run tiers are drawn from the same window.
|
|
702
|
+
contextWindow: contextWindowTokens({ model: profile.model, ...(profile.baseUrl !== undefined ? { baseUrl: profile.baseUrl } : {}) }),
|
|
669
703
|
// OR-1: the endpoint is the fourth passenger — the cost
|
|
670
704
|
// path and the window lookup key on (model, endpoint).
|
|
671
705
|
...(profile.baseUrl !== undefined ? { baseUrl: profile.baseUrl } : {}),
|
|
672
706
|
...(scope !== undefined ? { scope } : {}),
|
|
673
707
|
...(reasoning !== undefined ? { reasoning } : {}),
|
|
708
|
+
// 0.40.0: the NAME the person switched to, recorded for
|
|
709
|
+
// display — a direct provider/model has none.
|
|
710
|
+
profileName: direct === null ? profName : null,
|
|
674
711
|
};
|
|
675
712
|
ctx.session.setModelBinding(binding);
|
|
676
713
|
setLastBinding(binding);
|
|
@@ -732,7 +769,12 @@ export function dispatch(line, ctx) {
|
|
|
732
769
|
};
|
|
733
770
|
ctx.input.onEscape(onEscape);
|
|
734
771
|
let compactStart = 0;
|
|
735
|
-
let
|
|
772
|
+
let repaintCompacting = () => { };
|
|
773
|
+
// 0.40.0 (the owner's dogfood): the row walks the WORKING twinkle,
|
|
774
|
+
// the same spinner as a running turn — a static glyph repainted
|
|
775
|
+
// once a second read as a stalled screen while the model thought
|
|
776
|
+
let glyph = STATUS_GLYPHS[0];
|
|
777
|
+
let stopSpinner = null;
|
|
736
778
|
// the `as` on the initializer keeps the flow type the full union —
|
|
737
779
|
// onStart fills this during the call, but a closure assignment
|
|
738
780
|
// never re-narrows the outer scope (it would read `never`)
|
|
@@ -740,20 +782,30 @@ export function dispatch(line, ctx) {
|
|
|
740
782
|
// the ctx estimate BEFORE the summarized event lands (the used
|
|
741
783
|
// fraction — the recap's "ctx 91% → 34%" drops after compacting)
|
|
742
784
|
let ctxBefore = null;
|
|
785
|
+
// 0.40.0: the summary call's progress against its output budget —
|
|
786
|
+
// the latest report, read by the row; the runtime reports per
|
|
787
|
+
// streamed chunk and the row repaints at most every 200 ms
|
|
788
|
+
let progress = null;
|
|
789
|
+
let lastProgressPaint = 0;
|
|
743
790
|
const compacting = (info) => {
|
|
744
|
-
|
|
791
|
+
// 0.40.0: composed by the row seam, not a template here — the row
|
|
792
|
+
// is where the launch build's progress segment plugs in.
|
|
793
|
+
const text = (elapsed) => compactingStatus(glyph, info.rounds, info.tokens, elapsed, undefined, retryOnRow(), progress);
|
|
794
|
+
repaintCompacting = () => dock.setStatus(text(Math.round((Date.now() - compactStart) / 1000)), "esc to cancel");
|
|
745
795
|
compactStart = Date.now();
|
|
746
796
|
ctxBefore = ctxPercent(ctx.estimateCtx());
|
|
747
797
|
dock.setStatus(text(0), "esc to cancel");
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
798
|
+
stopSpinner = startStatusSpinner((next) => {
|
|
799
|
+
glyph = next;
|
|
800
|
+
repaintCompacting();
|
|
801
|
+
});
|
|
751
802
|
};
|
|
752
803
|
try {
|
|
753
804
|
// R3a: /compact <focus> — the words after the command steer
|
|
754
805
|
// the summary ("keep the auth details"); bare /compact is
|
|
755
806
|
// byte-identical to the pre-round call.
|
|
756
807
|
const focus = trimmed.slice(8).trim();
|
|
808
|
+
setRetryShown(null);
|
|
757
809
|
const result = await ctx.session.summarize({
|
|
758
810
|
signal: abort.signal,
|
|
759
811
|
// 0.39.2: the gesture gets the measured output budget and
|
|
@@ -767,6 +819,14 @@ export function dispatch(line, ctx) {
|
|
|
767
819
|
compactInfo = info;
|
|
768
820
|
compacting(info);
|
|
769
821
|
},
|
|
822
|
+
onProgress: (p) => {
|
|
823
|
+
progress = { produced: p.produced, budget: p.budget, reasoningUnseen: p.reasoningUnseen };
|
|
824
|
+
const now = Date.now();
|
|
825
|
+
if (now - lastProgressPaint < 200)
|
|
826
|
+
return;
|
|
827
|
+
lastProgressPaint = now;
|
|
828
|
+
repaintCompacting();
|
|
829
|
+
},
|
|
770
830
|
});
|
|
771
831
|
if (result === null) {
|
|
772
832
|
body.notice("[/compact] nothing to compact — fewer than 5 rounds yet");
|
|
@@ -828,8 +888,10 @@ export function dispatch(line, ctx) {
|
|
|
828
888
|
}
|
|
829
889
|
}
|
|
830
890
|
finally {
|
|
831
|
-
|
|
832
|
-
|
|
891
|
+
stopSpinner?.();
|
|
892
|
+
// ADR-0005 Amendment 2: a retry belongs to the call that
|
|
893
|
+
// announced it — none may outlive the compaction into the idle row.
|
|
894
|
+
setRetryShown(null);
|
|
833
895
|
ctx.paintIdle();
|
|
834
896
|
}
|
|
835
897
|
ctx.input.prompt();
|
|
@@ -910,8 +972,15 @@ export function dispatch(line, ctx) {
|
|
|
910
972
|
// would; the validation is the difference
|
|
911
973
|
if (!ctx.sessions().includes(arg)) {
|
|
912
974
|
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
913
|
-
|
|
914
|
-
|
|
975
|
+
const route = ctx.route?.(arg) ?? null;
|
|
976
|
+
if (route === null || route.kind === "refused") {
|
|
977
|
+
bodyLog(route?.line ?? `no such session: ${escapeTerminal(arg)} — /resume lists them`);
|
|
978
|
+
ctx.input.prompt();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if (route.kind === "elsewhere" && route.line !== null)
|
|
982
|
+
bodyLog(route.line);
|
|
983
|
+
ctx.requestSwitch(arg);
|
|
915
984
|
});
|
|
916
985
|
return;
|
|
917
986
|
}
|
|
@@ -933,6 +1002,16 @@ export function dispatch(line, ctx) {
|
|
|
933
1002
|
ctx.input.prompt(); // esc — nothing switched, nothing said
|
|
934
1003
|
return;
|
|
935
1004
|
}
|
|
1005
|
+
// 0.40.0: `tab` lists every project; another project's recorded
|
|
1006
|
+
// session is refused with where to go
|
|
1007
|
+
const route = ctx.sessions().includes(picked) ? null : (ctx.route?.(picked) ?? null);
|
|
1008
|
+
if (route?.kind === "refused") {
|
|
1009
|
+
bodyLog(route.line);
|
|
1010
|
+
ctx.input.prompt();
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (route?.kind === "elsewhere" && route.line !== null)
|
|
1014
|
+
bodyLog(route.line);
|
|
936
1015
|
ctx.requestSwitch(picked);
|
|
937
1016
|
});
|
|
938
1017
|
return;
|
|
@@ -945,6 +1024,25 @@ export function dispatch(line, ctx) {
|
|
|
945
1024
|
});
|
|
946
1025
|
return;
|
|
947
1026
|
}
|
|
1027
|
+
// 0.40.0 — a person's skill: `/skills`, `/skill <name> [args]`, or
|
|
1028
|
+
// `/<name> [args]` for a name no built-in above claimed. Placed AFTER
|
|
1029
|
+
// every built-in so a built-in always wins, and BEFORE the unknown-
|
|
1030
|
+
// command line so an installed skill is never called unknown.
|
|
1031
|
+
const skill = resolveSkillLine(trimmed, loadedSkillsCatalog(), slashCommandNames());
|
|
1032
|
+
if (trimmed === "/skills" || trimmed === "/skill" || skill !== null) {
|
|
1033
|
+
if (skill?.kind === "submit") {
|
|
1034
|
+
ctx.submitTurn(skill.content, skill.via);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
1038
|
+
if (skill?.kind === "list")
|
|
1039
|
+
bodyLog(skillsRows(loadedSkillsCatalog(), skillSource, tildePath(join(kisoHome(), "skills"))).map(escapeTerminal).join("\n"));
|
|
1040
|
+
else if (skill?.kind === "error")
|
|
1041
|
+
bodyLog(escapeTerminal(skill.message));
|
|
1042
|
+
ctx.input.prompt();
|
|
1043
|
+
});
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
948
1046
|
// PH-1a (finding PH-F1): an unrecognized slash command is an ERROR,
|
|
949
1047
|
// never a turn — the fallthrough used to hand "/clear", "/exit", or a
|
|
950
1048
|
// typo to the model, burning a request on text the user meant as a
|
package/dist/floor.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.40.0 — the catastrophe floor (launch-weekend plan §4, minimum shape;
|
|
3
|
+
* owner, 2026-09-17: "bypass stays bypass: `rm -rf /tmp/probe` runs. The
|
|
4
|
+
* floor refuses only unrecoverable targets"; the lead's review and rulings
|
|
5
|
+
* of 2026-09-18).
|
|
6
|
+
*
|
|
7
|
+
* A destructive command whose target cannot be recovered is REFUSED, in
|
|
8
|
+
* every mode, bypass included, as a precondition — a chain member that
|
|
9
|
+
* denies, and a deny is what nothing outranks. The destructive commands:
|
|
10
|
+
* `rm`; `git clean -f`, `git reset --hard`, `git checkout -- <paths>` and
|
|
11
|
+
* the same loss spelled `git checkout .` / `-f`, `git restore` touching
|
|
12
|
+
* the worktree, `git switch -f` (R5); `find … -delete` with no selecting
|
|
13
|
+
* primary before it (R6). The unrecoverable targets:
|
|
14
|
+
*
|
|
15
|
+
* - `/`, the home directory, the workspace root or any directory above it;
|
|
16
|
+
* - the workspace's `.git` (R7);
|
|
17
|
+
* - `~/.ssh`, `~/.config`, `~/.kiso`, `~/.gnupg`, `~/.aws`, or inside one;
|
|
18
|
+
* - a system root and what is inside it — except the temp family and the
|
|
19
|
+
* software prefixes (/opt/homebrew, /usr/local, linuxbrew), where only
|
|
20
|
+
* the root itself is, and /Volumes /Users /home /mnt /media, where the
|
|
21
|
+
* root and its direct children are (R8). Inside the workspace or the
|
|
22
|
+
* home directory is never a system root's;
|
|
23
|
+
* - a wildcard over any of those (`~/*`, `/*`, `*` at the workspace root —
|
|
24
|
+
* a component of nothing but globs; `*.log` ranges over SOME entries);
|
|
25
|
+
* - a target that is only a variable (`$DIR/`, `"$X"/*`) — empty, it is
|
|
26
|
+
* the root of whatever comes after it. `$HOME` and `$PWD` are known.
|
|
27
|
+
*
|
|
28
|
+
* This is a reading of the command line, not a guarantee: the sandbox is
|
|
29
|
+
* the guarantee, and it comes after the launch. So the reading errs toward
|
|
30
|
+
* seeing a command — it looks inside `$( )`, backticks, `( )`, `sh -c` and
|
|
31
|
+
* `eval`, through `sudo`/`env`/`timeout`/`command` and shell keywords, and
|
|
32
|
+
* follows `cd` by its joiner — and a line that knocks the reader out is
|
|
33
|
+
* denied. Documented, not refused: `xargs rm`, script files and heredocs,
|
|
34
|
+
* `find -exec rm`, `mv`, `chmod -R`, `rmdir`, `unlink`, `truncate`,
|
|
35
|
+
* `shred`, `>` truncation, `rsync --delete`, `npx rimraf`, `trash`,
|
|
36
|
+
* `fish -c`, `ssh`, `docker`, `node -e` / `python -c` / `perl -e`, brace
|
|
37
|
+
* expansion, `$'…'`, `~+`, popd past one level, nesting past 16.
|
|
38
|
+
*
|
|
39
|
+
* The same resolver as the read-only allow decides where a path goes.
|
|
40
|
+
* Default on; `"floor": "off"` in the USER config turns it off, and a
|
|
41
|
+
* project config cannot — a repository must not be able to lower it.
|
|
42
|
+
*/
|
|
43
|
+
import type { PolicyCall } from "@vincemakes/kiso-core";
|
|
44
|
+
import type { KisoExtension } from "@vincemakes/kiso-runtime";
|
|
45
|
+
import { type LooseWord } from "./shell-words.js";
|
|
46
|
+
export type FloorVerdict = {
|
|
47
|
+
readonly refused: false;
|
|
48
|
+
} | {
|
|
49
|
+
readonly refused: true;
|
|
50
|
+
readonly why: string;
|
|
51
|
+
};
|
|
52
|
+
/** Past keywords, `sudo -u x`, `env A=1`, `timeout 5`, `command`, … to the
|
|
53
|
+
* command that runs. Matched by basename: `/usr/bin/env rm` is env. */
|
|
54
|
+
export declare function unwrap(argv: readonly LooseWord[]): readonly LooseWord[];
|
|
55
|
+
/** `sh -c '…'`, `bash -lc '…'`, `eval …`: the command lines inside.
|
|
56
|
+
* eval CONCATENATES its words with spaces and parses the result again —
|
|
57
|
+
* that is its meaning, and `eval 'rm -rf ~'` is read that way. It is also
|
|
58
|
+
* read once more with each word re-quoted, so `eval sh -c 'rm -rf /'`
|
|
59
|
+
* keeps its script whole (B10, the lead's reading): either refusing is a
|
|
60
|
+
* refusal. In the re-quoted reading `~` is already expanded and a word
|
|
61
|
+
* holding an unknown stays raw — its value is unknown to eval too. */
|
|
62
|
+
export declare function innerLines(argv: readonly LooseWord[], home: string): {
|
|
63
|
+
readonly lines: readonly string[];
|
|
64
|
+
readonly subshell: boolean;
|
|
65
|
+
} | null;
|
|
66
|
+
/**
|
|
67
|
+
* Would the floor refuse this command line? `workspaceRoot` is where the
|
|
68
|
+
* shell runs it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function floorCheck(commandLine: string, workspaceRoot: string, home?: string): FloorVerdict;
|
|
71
|
+
/** Is there a destructive command anywhere in the line, whatever its
|
|
72
|
+
* target? A saved allow is never inherited by one (plan §4). */
|
|
73
|
+
export declare function isDestructive(commandLine: string): boolean;
|
|
74
|
+
/** The chain member, at the chain's head: DENY or ABSTAIN. */
|
|
75
|
+
export declare function floorExtension(on: () => boolean, workspaceRoot: () => string, check?: typeof floorCheck): KisoExtension;
|
|
76
|
+
/** Plan §4: "destructive commands never inherit a saved allow in the
|
|
77
|
+
* asking modes" — the predicate the saved-allow wrap
|
|
78
|
+
* (protected-writes.ts) is given, keyed on destructiveness, not on the
|
|
79
|
+
* target: a saved allow never runs even `rm -rf build` unasked. */
|
|
80
|
+
export declare function isDestructiveCall(call: PolicyCall): boolean;
|