@vincemakes/kiso-code 0.24.5 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat.js +3 -0
- package/dist/clipboard.d.ts +52 -0
- package/dist/clipboard.js +78 -0
- package/dist/dispatch.js +62 -42
- package/dist/index.js +24 -1
- package/dist/state.d.ts +8 -0
- package/dist/state.js +45 -5
- package/package.json +14 -14
package/dist/chat.js
CHANGED
|
@@ -1193,6 +1193,9 @@ export async function chat(session, faux, input, autoCompact, nav) {
|
|
|
1193
1193
|
// chain action (the sentinel's control char marks the key, so a typed
|
|
1194
1194
|
// "expand" turn is never intercepted).
|
|
1195
1195
|
input.onExpand(() => dispatch("\x12expand", dispatchCtx));
|
|
1196
|
+
// E1 §3 — ctrl+x and `/copy` are the same action reached two ways, so
|
|
1197
|
+
// they are the same sentinel: one implementation, one behaviour.
|
|
1198
|
+
input.onCopy(() => dispatch("\x18copy", dispatchCtx));
|
|
1196
1199
|
// R3a — Shift+Tab: the approval-tier cycle (the /mode ring, in the
|
|
1197
1200
|
// MODES order). The switch is the SAME live-extension flip /mode
|
|
1198
1201
|
// performs; the status row repaints at once with a one-line notice.
|
package/dist/clipboard.d.ts
CHANGED
|
@@ -35,3 +35,55 @@
|
|
|
35
35
|
export declare function clipboardImage(dir: string, run?: (cmd: string, args: readonly string[]) => {
|
|
36
36
|
status: number | null;
|
|
37
37
|
}): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* E1 §3 — THE LAST ANSWER, from the record rather than from the screen.
|
|
40
|
+
*
|
|
41
|
+
* The source is the session's PROJECTION (`session.projected()`), which
|
|
42
|
+
* is derived from committed events: a message a retry or an abort voided
|
|
43
|
+
* is already absent from it, and so is anything still streaming. Two
|
|
44
|
+
* things this deliberately is NOT:
|
|
45
|
+
*
|
|
46
|
+
* - the rendered rows, which are folded, washed and cut — a paste of
|
|
47
|
+
* those is a paste of the terminal's layout, not of the answer;
|
|
48
|
+
* - a join of the trailing `text_delta` frames, which are a live
|
|
49
|
+
* projection and not a record.
|
|
50
|
+
*
|
|
51
|
+
* Null when there is no complete answer yet, so the caller can say so
|
|
52
|
+
* rather than copying an empty string over whatever the user had.
|
|
53
|
+
*/
|
|
54
|
+
export declare function lastAnswer(messages: readonly import("@vincemakes/kiso-core").Message[]): string | null;
|
|
55
|
+
/** What the clipboard route did, and the one sentence the status row may
|
|
56
|
+
* say about it. The message is the product of this function because the
|
|
57
|
+
* claim and the act must not be able to drift apart. */
|
|
58
|
+
export interface ClipboardWrite {
|
|
59
|
+
readonly ok: boolean;
|
|
60
|
+
readonly message: string;
|
|
61
|
+
}
|
|
62
|
+
interface WriteEnv {
|
|
63
|
+
readonly platform: string;
|
|
64
|
+
readonly isTTY: boolean;
|
|
65
|
+
readonly run: (cmd: string, args: readonly string[], stdin?: string) => {
|
|
66
|
+
status: number | null;
|
|
67
|
+
};
|
|
68
|
+
readonly write: (s: string) => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* E1 §3 — PUT TEXT ON THE CLIPBOARD, and say only what happened.
|
|
72
|
+
*
|
|
73
|
+
* Two routes, and they differ in what they can KNOW:
|
|
74
|
+
*
|
|
75
|
+
* - **darwin, `pbcopy`**: a real process with a real exit status. When
|
|
76
|
+
* it succeeds we can say `copied`. When it fails we say so.
|
|
77
|
+
* - **everywhere else, OSC 52**: a REQUEST to the terminal. Most
|
|
78
|
+
* terminals do not answer it and Apple Terminal does not implement
|
|
79
|
+
* it at all, so there is no success to report — only that we asked.
|
|
80
|
+
* The row says `asked the terminal to copy N chars`. An earlier
|
|
81
|
+
* draft said `copied N chars (via the terminal)`, which still leads
|
|
82
|
+
* with "copied"; a reader takes the first word.
|
|
83
|
+
*
|
|
84
|
+
* And a route that does not exist: **stdout that is not a TTY emits
|
|
85
|
+
* nothing**. An OSC written into a pipe is escape bytes in someone's
|
|
86
|
+
* data.
|
|
87
|
+
*/
|
|
88
|
+
export declare function clipboardWrite(text: string, env?: Partial<WriteEnv>): ClipboardWrite;
|
|
89
|
+
export {};
|
package/dist/clipboard.js
CHANGED
|
@@ -68,3 +68,81 @@ export function clipboardImage(dir, run = defaultRun) {
|
|
|
68
68
|
function defaultRun(cmd, args) {
|
|
69
69
|
return spawnSync(cmd, [...args], { stdio: "ignore", timeout: 5000 });
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* E1 §3 — THE LAST ANSWER, from the record rather than from the screen.
|
|
73
|
+
*
|
|
74
|
+
* The source is the session's PROJECTION (`session.projected()`), which
|
|
75
|
+
* is derived from committed events: a message a retry or an abort voided
|
|
76
|
+
* is already absent from it, and so is anything still streaming. Two
|
|
77
|
+
* things this deliberately is NOT:
|
|
78
|
+
*
|
|
79
|
+
* - the rendered rows, which are folded, washed and cut — a paste of
|
|
80
|
+
* those is a paste of the terminal's layout, not of the answer;
|
|
81
|
+
* - a join of the trailing `text_delta` frames, which are a live
|
|
82
|
+
* projection and not a record.
|
|
83
|
+
*
|
|
84
|
+
* Null when there is no complete answer yet, so the caller can say so
|
|
85
|
+
* rather than copying an empty string over whatever the user had.
|
|
86
|
+
*/
|
|
87
|
+
export function lastAnswer(messages) {
|
|
88
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
89
|
+
const m = messages[i];
|
|
90
|
+
if (m.role !== "assistant")
|
|
91
|
+
continue;
|
|
92
|
+
// A turn that only called tools said nothing to copy. Its blocks
|
|
93
|
+
// are real, so `blocks.length > 0` is not the test — the test is
|
|
94
|
+
// whether any of them is TEXT.
|
|
95
|
+
const text = m.blocks
|
|
96
|
+
.filter((b) => b.type === "text")
|
|
97
|
+
.map((b) => b.text)
|
|
98
|
+
.join("\n\n");
|
|
99
|
+
if (text !== "")
|
|
100
|
+
return text;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* E1 §3 — PUT TEXT ON THE CLIPBOARD, and say only what happened.
|
|
106
|
+
*
|
|
107
|
+
* Two routes, and they differ in what they can KNOW:
|
|
108
|
+
*
|
|
109
|
+
* - **darwin, `pbcopy`**: a real process with a real exit status. When
|
|
110
|
+
* it succeeds we can say `copied`. When it fails we say so.
|
|
111
|
+
* - **everywhere else, OSC 52**: a REQUEST to the terminal. Most
|
|
112
|
+
* terminals do not answer it and Apple Terminal does not implement
|
|
113
|
+
* it at all, so there is no success to report — only that we asked.
|
|
114
|
+
* The row says `asked the terminal to copy N chars`. An earlier
|
|
115
|
+
* draft said `copied N chars (via the terminal)`, which still leads
|
|
116
|
+
* with "copied"; a reader takes the first word.
|
|
117
|
+
*
|
|
118
|
+
* And a route that does not exist: **stdout that is not a TTY emits
|
|
119
|
+
* nothing**. An OSC written into a pipe is escape bytes in someone's
|
|
120
|
+
* data.
|
|
121
|
+
*/
|
|
122
|
+
export function clipboardWrite(text, env = {}) {
|
|
123
|
+
const platform = env.platform ?? process.platform;
|
|
124
|
+
const isTTY = env.isTTY ?? process.stdout.isTTY === true;
|
|
125
|
+
const run = env.run ?? ((cmd, args, stdin) => spawnSync(cmd, [...args], { input: stdin, stdio: ["pipe", "ignore", "ignore"], timeout: 5000 }));
|
|
126
|
+
const write = env.write ?? ((s) => process.stdout.write(s));
|
|
127
|
+
const n = [...text].length;
|
|
128
|
+
// NO TTY, NO COPY — and this is checked BEFORE the platform, which is
|
|
129
|
+
// the correction the pipe gate forced.
|
|
130
|
+
//
|
|
131
|
+
// The first build let darwin's `pbcopy` run regardless, on the
|
|
132
|
+
// reasoning that pbcopy is a process and not a terminal route so it
|
|
133
|
+
// works with stdout redirected. It does work — and that is the
|
|
134
|
+
// problem: a script running `kiso chat < input.txt` would silently
|
|
135
|
+
// overwrite whatever the human had on their clipboard. A side effect
|
|
136
|
+
// on the user's desktop is not something a pipe should be able to
|
|
137
|
+
// cause by accident.
|
|
138
|
+
if (!isTTY)
|
|
139
|
+
return { ok: false, message: "no terminal to copy to" };
|
|
140
|
+
if (platform === "darwin") {
|
|
141
|
+
const r = run("pbcopy", [], text);
|
|
142
|
+
return r.status === 0
|
|
143
|
+
? { ok: true, message: `copied ${n} chars` }
|
|
144
|
+
: { ok: false, message: `could not copy — pbcopy exited ${r.status ?? "abnormally"}` };
|
|
145
|
+
}
|
|
146
|
+
write(`\x1b]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`);
|
|
147
|
+
return { ok: true, message: `asked the terminal to copy ${n} chars` };
|
|
148
|
+
}
|
package/dist/dispatch.js
CHANGED
|
@@ -7,6 +7,7 @@ import { contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpR
|
|
|
7
7
|
import { newSessionId } from "./session-id.js";
|
|
8
8
|
import { buildAdapter, resolveContinuationScope, resolveReasoning } from "@vincemakes/kiso-runtime/internal";
|
|
9
9
|
import { MODES, MODE_NOTE, getMode, setMode } from "./mode.js";
|
|
10
|
+
import { clipboardWrite, lastAnswer } from "./clipboard.js";
|
|
10
11
|
import { agentModel, body, bodyLog, configModels, dock, readContextLedger, sessionsDir, setAgentModel, setCurrentModelName, setLastBinding } from "./state.js";
|
|
11
12
|
import { directWriteProfile, profileAvailable } from "./config.js";
|
|
12
13
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
@@ -82,51 +83,70 @@ export function dispatch(line, ctx) {
|
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
85
|
if (trimmed === "\x12expand") {
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
86
|
+
// DC-50 / R14 — ONE SWITCH, AND THE SESSION IS PRINTED AGAIN.
|
|
87
|
+
//
|
|
88
|
+
// What stood here: the key picked a TARGET at press time. A live
|
|
89
|
+
// tool cell toggled in place; a committed one could not, because
|
|
90
|
+
// ADR-0046 §3 forbade rewriting history — so its body was
|
|
91
|
+
// APPENDED as new content, chained behind any in-flight turn,
|
|
92
|
+
// with a ring choosing which card was next, `#opened` keeping the
|
|
93
|
+
// ring from repeating, `#lastAppend` stopping a held key printing
|
|
94
|
+
// the same rows three times (DC-35), and a two-branch "nothing to
|
|
95
|
+
// expand" message for the two ways it could decline.
|
|
96
|
+
//
|
|
97
|
+
// Amendment 1 removes the premise the whole apparatus served: the
|
|
98
|
+
// terminal's scrollback is ours to erase, so a committed card can
|
|
99
|
+
// be re-rendered where it stands. The key flips one boolean and
|
|
100
|
+
// the session is reprinted; every settled card obeys it, running
|
|
101
|
+
// cards keep their own window (E2/DC-43).
|
|
102
|
+
//
|
|
103
|
+
// It no longer chains. There is no content to land after an
|
|
104
|
+
// in-flight turn — nothing is appended — so the repaint is
|
|
105
|
+
// immediate, exactly as the live-cell toggle always was.
|
|
106
|
+
body.toggleExpanded();
|
|
107
|
+
ctx.input.prompt();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (trimmed === "\x18copy" || trimmed === "/copy") {
|
|
111
|
+
// E1 §3 — the last answer onto the clipboard, as RAW MARKDOWN.
|
|
112
|
+
//
|
|
113
|
+
// The source is the session's PROJECTION, which is derived from
|
|
114
|
+
// committed events: a message a retry or an abort voided is already
|
|
115
|
+
// absent from it, and anything still streaming has not landed in it
|
|
116
|
+
// yet. Deliberately NOT the rendered rows (folded, washed, cut —
|
|
117
|
+
// that is the terminal's layout, not the answer) and NOT a join of
|
|
118
|
+
// the trailing text_delta frames (a live projection, not a record).
|
|
119
|
+
//
|
|
120
|
+
// One implementation for the key and the command: they are the same
|
|
121
|
+
// action reached two ways, and two implementations would be two
|
|
122
|
+
// behaviours waiting to drift.
|
|
123
|
+
// IT CHAINS, like `/last` and unlike `ctrl+o`. The distinction is
|
|
124
|
+
// what the action DEPENDS ON: ctrl+o repaints what is already on
|
|
125
|
+
// screen and can answer at once, while a copy reads the last
|
|
126
|
+
// ANSWER — which may be the turn currently in flight.
|
|
127
|
+
//
|
|
128
|
+
// Found by the pipe gate: a pipe hands the CLI every line at once,
|
|
129
|
+
// so an unchained `/copy` typed after `go` ran BEFORE the turn and
|
|
130
|
+
// said "nothing to copy — no answer yet" above the answer it was
|
|
131
|
+
// asked to copy.
|
|
132
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
133
|
+
const answer = lastAnswer(ctx.session.projected());
|
|
134
|
+
if (answer === null) {
|
|
135
|
+
bodyLog("[nothing to copy — no answer yet]");
|
|
119
136
|
}
|
|
120
|
-
else
|
|
121
|
-
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
137
|
+
else {
|
|
138
|
+
const r = clipboardWrite(answer);
|
|
139
|
+
// The MESSAGE comes from clipboardWrite, never from here —
|
|
140
|
+
// the claim and the act must not be able to drift apart. On
|
|
141
|
+
// the dock it rides the W18 hint slot (gone at the next
|
|
142
|
+
// keypress); in a pipe it is a line like any other.
|
|
143
|
+
if (dock.active)
|
|
144
|
+
dock.setStatus(r.message);
|
|
145
|
+
else
|
|
146
|
+
bodyLog(`[${r.message}]`);
|
|
126
147
|
}
|
|
127
148
|
ctx.input.prompt();
|
|
128
|
-
};
|
|
129
|
-
ctx.chainRef.current = ctx.chainRef.current.then(land);
|
|
149
|
+
});
|
|
130
150
|
return;
|
|
131
151
|
}
|
|
132
152
|
if (trimmed === "/rewrap") {
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,10 @@ function readlineInput(rl) {
|
|
|
84
84
|
/* readline has no ctrl+o binding — ignored (W15 rides the
|
|
85
85
|
* editor path only). */
|
|
86
86
|
},
|
|
87
|
+
onCopy() {
|
|
88
|
+
/* readline has no ctrl+x binding — ignored. `/copy` still
|
|
89
|
+
* works here: it is a typed command, not a key. */
|
|
90
|
+
},
|
|
87
91
|
question(query, cb) {
|
|
88
92
|
rl.question(query, cb);
|
|
89
93
|
},
|
|
@@ -164,6 +168,9 @@ function editorInput(editor) {
|
|
|
164
168
|
onExpand(cb) {
|
|
165
169
|
editor.onExpand(cb);
|
|
166
170
|
},
|
|
171
|
+
onCopy(cb) {
|
|
172
|
+
editor.onCopy(cb);
|
|
173
|
+
},
|
|
167
174
|
// KC2 §2: the redirect gesture — the editor decides WHEN (the
|
|
168
175
|
// same-chunk pair, the precedence gate); chat decides what it MEANS.
|
|
169
176
|
onRedirect(cb) {
|
|
@@ -402,10 +409,26 @@ function extensionsBanner(resume = []) {
|
|
|
402
409
|
// filesystem calls it.
|
|
403
410
|
const home = homedir();
|
|
404
411
|
const cwd = process.cwd();
|
|
412
|
+
/** DC-49: realpath, falling back to the raw path when it cannot be
|
|
413
|
+
* resolved — an unresolvable path is not a match, and throwing here
|
|
414
|
+
* would take the banner down over a cosmetic row. */
|
|
415
|
+
const realOf = (dir) => {
|
|
416
|
+
try {
|
|
417
|
+
return realpathSync(dir);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return dir;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
405
423
|
body.banner(VERSION, text.replace(/^ · /, ""), resume, {
|
|
406
424
|
model: agentModel,
|
|
407
425
|
mode: getMode() === "plan" ? "plan (read-only)" : getMode(),
|
|
408
426
|
cwd: cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd,
|
|
427
|
+
// DC-49 — REALPATH on both sides. A symlinked HOME (or a symlinked
|
|
428
|
+
// cwd) compares unequal as raw strings while being the same
|
|
429
|
+
// directory, and the row would then be absent exactly where it is
|
|
430
|
+
// most needed.
|
|
431
|
+
homeWorkspace: realOf(cwd) === realOf(home),
|
|
409
432
|
});
|
|
410
433
|
}
|
|
411
434
|
/**
|
|
@@ -578,7 +601,7 @@ async function makeAgent(sessionId, input, modelFlag) {
|
|
|
578
601
|
store,
|
|
579
602
|
// Area 5: the coding tools are bound to the workspace — every path
|
|
580
603
|
// they touch is canonicalized inside cwd, escapes are refused.
|
|
581
|
-
tools: [...createCodingTools({ workspaceRoot: process.cwd() })],
|
|
604
|
+
tools: [...createCodingTools({ workspaceRoot: process.cwd(), excludeRoots: [kisoHome()] })], // DC-49
|
|
582
605
|
// Modes: the five tiers ride the E1 policy chain (mode:<tier>
|
|
583
606
|
// extensions, current tier first) — the old static PERMISSION_POLICY
|
|
584
607
|
// is gone, its semantics live in the "default" tier. The banner
|
package/dist/state.d.ts
CHANGED
|
@@ -55,6 +55,10 @@ export declare function readContextLedger(sessionId: string, window: number): im
|
|
|
55
55
|
* the fuzzy filter sees one alphabet whichever branch ran.
|
|
56
56
|
*/
|
|
57
57
|
export declare function atFiles(): readonly AtItem[];
|
|
58
|
+
/** DC-49 — the walk, addressable for the gate. `atFiles` is driven by
|
|
59
|
+
* `process.cwd()` and `kisoHome()`, neither of which a unit case can
|
|
60
|
+
* set honestly; this is the same function with both supplied. */
|
|
61
|
+
export declare function atWalkFor(root: string, excludeRoots: readonly string[]): readonly string[];
|
|
58
62
|
/**
|
|
59
63
|
* v2c — the interactive input source. TTYs use the raw-mode Editor (the
|
|
60
64
|
* self-drawn input row — width-aware, the CJK-drift root cause retired,
|
|
@@ -70,6 +74,10 @@ export interface LineInput {
|
|
|
70
74
|
/** W15: the expand key (ctrl+o) — the chain-level action, never the
|
|
71
75
|
* editor's own interpretation. */
|
|
72
76
|
onExpand(cb: () => void): void;
|
|
77
|
+
/** E1 §3: the copy key (ctrl+x) — the chain-level action, never the
|
|
78
|
+
* composer's own. The readline input ignores it; `/copy` is the
|
|
79
|
+
* route that works everywhere. */
|
|
80
|
+
onCopy(cb: () => void): void;
|
|
73
81
|
/** KC2 §2: the redirect gesture (Alt+Enter / Ctrl+Enter) — the
|
|
74
82
|
* buffer's text arrives as a line at the same instant the run is told
|
|
75
83
|
* to stop. OPTIONAL: the pipe path has no raw keys and never wires
|
package/dist/state.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the moved modules read and mutate at call time.
|
|
7
7
|
*/
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
9
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
@@ -119,7 +119,8 @@ export function atFiles() {
|
|
|
119
119
|
paths = execFileSync("git", ["ls-files", "-c", "-o", "--exclude-standard"], { cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 1 << 24 }).split("\n").filter((p) => p !== "");
|
|
120
120
|
}
|
|
121
121
|
catch {
|
|
122
|
-
|
|
122
|
+
// DC-49: the fallback walk excludes kiso's own state directory.
|
|
123
|
+
paths = atWalk(process.cwd(), "", [], [kisoHome()]);
|
|
123
124
|
}
|
|
124
125
|
return paths.slice(0, AT_CAP + 1).map((path) => ({ path }));
|
|
125
126
|
}
|
|
@@ -128,15 +129,26 @@ export function atFiles() {
|
|
|
128
129
|
* directory contributes nothing and ends nothing — the recursion's own
|
|
129
130
|
* try catches it at that level, so one locked subtree cannot stop a
|
|
130
131
|
* file picker from opening. */
|
|
131
|
-
function atWalk(dir, prefix, out) {
|
|
132
|
+
function atWalk(dir, prefix, out, excluded = []) {
|
|
132
133
|
try {
|
|
133
134
|
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
134
135
|
if (out.length > AT_CAP)
|
|
135
136
|
break;
|
|
136
137
|
if (AT_SKIP.has(e.name))
|
|
137
138
|
continue;
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
const full = `${dir}/${e.name}`;
|
|
140
|
+
if (e.isDirectory()) {
|
|
141
|
+
// DC-49 — the picker does not DESCEND into an excluded root.
|
|
142
|
+
//
|
|
143
|
+
// `AT_SKIP` is a list of NAMES with no dot rule, so unlike
|
|
144
|
+
// tools-node's walk this one has never skipped `~/.kiso` at
|
|
145
|
+
// all: with the workspace at `~` and no git repo there, `@`
|
|
146
|
+
// lists the user's own session logs among their files. That
|
|
147
|
+
// is current behaviour, not a future risk.
|
|
148
|
+
if (excluded.length > 0 && isUnder(full, excluded))
|
|
149
|
+
continue;
|
|
150
|
+
atWalk(full, `${prefix}${e.name}/`, out, excluded);
|
|
151
|
+
}
|
|
140
152
|
else if (e.isFile())
|
|
141
153
|
out.push(`${prefix}${e.name}`);
|
|
142
154
|
}
|
|
@@ -146,6 +158,34 @@ function atWalk(dir, prefix, out) {
|
|
|
146
158
|
}
|
|
147
159
|
return out;
|
|
148
160
|
}
|
|
161
|
+
/** DC-49 — realpath on BOTH sides. darwin's `/tmp` is a symlink to
|
|
162
|
+
* `/private/tmp`, so a raw string compare is a check that passes on one
|
|
163
|
+
* machine and not another. */
|
|
164
|
+
function isUnder(dir, roots) {
|
|
165
|
+
let real;
|
|
166
|
+
try {
|
|
167
|
+
real = realpathSync(dir);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
real = dir;
|
|
171
|
+
}
|
|
172
|
+
return roots.some((r) => {
|
|
173
|
+
let rr;
|
|
174
|
+
try {
|
|
175
|
+
rr = realpathSync(r);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
rr = r;
|
|
179
|
+
}
|
|
180
|
+
return real === rr || real.startsWith(`${rr}/`);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** DC-49 — the walk, addressable for the gate. `atFiles` is driven by
|
|
184
|
+
* `process.cwd()` and `kisoHome()`, neither of which a unit case can
|
|
185
|
+
* set honestly; this is the same function with both supplied. */
|
|
186
|
+
export function atWalkFor(root, excludeRoots) {
|
|
187
|
+
return atWalk(root, "", [], excludeRoots);
|
|
188
|
+
}
|
|
149
189
|
/** v2b: the bottom-anchored UI — docked only on a color TTY; pipes and
|
|
150
190
|
* NO_COLOR stay the v2a line mode byte-for-byte. Created at load, like
|
|
151
191
|
* the pre-split module-scope const. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "kiso CLI — the durable coding agent that survives kill -9: kiso chat / kiso resume / kiso sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-ask-ext": "0.
|
|
22
|
-
"@vincemakes/kiso-core": "0.
|
|
23
|
-
"@vincemakes/kiso-evals": "0.
|
|
24
|
-
"@vincemakes/kiso-mcp-ext": "0.
|
|
25
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
26
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
27
|
-
"@vincemakes/kiso-runtime": "0.
|
|
28
|
-
"@vincemakes/kiso-skills-ext": "0.
|
|
29
|
-
"@vincemakes/kiso-subagent-ext": "0.
|
|
30
|
-
"@vincemakes/kiso-task-ext": "0.
|
|
31
|
-
"@vincemakes/kiso-tools-node": "0.
|
|
32
|
-
"@vincemakes/kiso-tui": "0.
|
|
33
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
21
|
+
"@vincemakes/kiso-ask-ext": "0.26.0",
|
|
22
|
+
"@vincemakes/kiso-core": "0.26.0",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.26.0",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.26.0",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.26.0",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.26.0",
|
|
27
|
+
"@vincemakes/kiso-runtime": "0.26.0",
|
|
28
|
+
"@vincemakes/kiso-skills-ext": "0.26.0",
|
|
29
|
+
"@vincemakes/kiso-subagent-ext": "0.26.0",
|
|
30
|
+
"@vincemakes/kiso-task-ext": "0.26.0",
|
|
31
|
+
"@vincemakes/kiso-tools-node": "0.26.0",
|
|
32
|
+
"@vincemakes/kiso-tui": "0.26.0",
|
|
33
|
+
"@vincemakes/kiso-tui-cells": "0.26.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.1.2",
|