@vincemakes/kiso-code 0.25.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat.js +13 -2
- package/dist/clipboard.d.ts +52 -0
- package/dist/clipboard.js +78 -0
- package/dist/dispatch.js +43 -0
- package/dist/index.js +68 -4
- package/dist/state.d.ts +11 -0
- package/dist/state.js +45 -5
- package/package.json +14 -14
package/dist/chat.js
CHANGED
|
@@ -1182,17 +1182,28 @@ export async function chat(session, faux, input, autoCompact, nav) {
|
|
|
1182
1182
|
return;
|
|
1183
1183
|
dispatch("/compact", dispatchCtx);
|
|
1184
1184
|
};
|
|
1185
|
+
// CX-1 F5: a literal input (task-file mode) submits the turn directly —
|
|
1186
|
+
// its bytes never meet the slash dispatcher.
|
|
1187
|
+
const route = (line) => {
|
|
1188
|
+
if (input.literal === true)
|
|
1189
|
+
dispatchCtx.submitTurn(line);
|
|
1190
|
+
else
|
|
1191
|
+
dispatch(line, dispatchCtx);
|
|
1192
|
+
};
|
|
1185
1193
|
input.onLine((line) => {
|
|
1186
1194
|
if (!replReady) {
|
|
1187
1195
|
queuedLines.push(line);
|
|
1188
1196
|
return;
|
|
1189
1197
|
}
|
|
1190
|
-
|
|
1198
|
+
route(line);
|
|
1191
1199
|
});
|
|
1192
1200
|
// W15: the expand key — the editor forwards ctrl+o; dispatch runs the
|
|
1193
1201
|
// chain action (the sentinel's control char marks the key, so a typed
|
|
1194
1202
|
// "expand" turn is never intercepted).
|
|
1195
1203
|
input.onExpand(() => dispatch("\x12expand", dispatchCtx));
|
|
1204
|
+
// E1 §3 — ctrl+x and `/copy` are the same action reached two ways, so
|
|
1205
|
+
// they are the same sentinel: one implementation, one behaviour.
|
|
1206
|
+
input.onCopy(() => dispatch("\x18copy", dispatchCtx));
|
|
1196
1207
|
// R3a — Shift+Tab: the approval-tier cycle (the /mode ring, in the
|
|
1197
1208
|
// MODES order). The switch is the SAME live-extension flip /mode
|
|
1198
1209
|
// performs; the status row repaints at once with a one-line notice.
|
|
@@ -1243,7 +1254,7 @@ export async function chat(session, faux, input, autoCompact, nav) {
|
|
|
1243
1254
|
// chain BEFORE the appends and the replayed turns would never be
|
|
1244
1255
|
// awaited (the F-group regression).
|
|
1245
1256
|
for (const line of queuedLines) {
|
|
1246
|
-
|
|
1257
|
+
route(line);
|
|
1247
1258
|
}
|
|
1248
1259
|
queuedLines.length = 0;
|
|
1249
1260
|
input.prompt();
|
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
|
|
@@ -106,6 +107,48 @@ export function dispatch(line, ctx) {
|
|
|
106
107
|
ctx.input.prompt();
|
|
107
108
|
return;
|
|
108
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]");
|
|
136
|
+
}
|
|
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}]`);
|
|
147
|
+
}
|
|
148
|
+
ctx.input.prompt();
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
109
152
|
if (trimmed === "/rewrap") {
|
|
110
153
|
// R4 (C4d): re-print the recent PROSE at the CURRENT width, at the
|
|
111
154
|
// bottom. kiso hard-folds every row before committing it (invariant
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { appendFileSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { newSessionId } from "./session-id.js";
|
|
28
28
|
import { createInterface } from "node:readline";
|
|
29
|
+
import { Readable } from "node:stream";
|
|
29
30
|
import { fileURLToPath } from "node:url";
|
|
30
31
|
import { join } from "node:path";
|
|
31
32
|
import { Body, Editor, bannerLines, currentGround, resolveGround, setGround, escapeTerminal, extensionsBannerText, idColumn, idleStatus, interactivePrompt, palette, renderSessionLine, sessionListFooter, sessionListRow } from "@vincemakes/kiso-tui";
|
|
@@ -38,7 +39,7 @@ import { agentModel, atFiles, body, bodyLog, kisoHome, builtInExtensions, curren
|
|
|
38
39
|
import { askUi, resolveProjectTrust } from "./trust-ui.js";
|
|
39
40
|
import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
|
|
40
41
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
41
|
-
import {
|
|
42
|
+
import { chat, contextWindowTokens, estimateCtxRatio } from "./chat.js";
|
|
42
43
|
import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
|
|
43
44
|
import { checkForUpdate } from "./update-check.js";
|
|
44
45
|
import { resume } from "./resume.js";
|
|
@@ -54,6 +55,22 @@ export { applyProjectMerges } from "./trust-ui.js";
|
|
|
54
55
|
* prompt. Only ever constructed when stdin is NOT a TTY. The rl starts
|
|
55
56
|
* consuming stdin at construction (main), so 'line' events are buffered
|
|
56
57
|
* until chat() wires the handler — pipe input must never be dropped. */
|
|
58
|
+
/** CX-1 F5 (audit F5): the task-file input — the file's whole content is
|
|
59
|
+
* ONE turn, delivered the moment the loop listens, and the input is
|
|
60
|
+
* closed right after: chat still awaits the in-flight chain before it
|
|
61
|
+
* exits, so the turn runs to its terminal and nothing else is read.
|
|
62
|
+
* Built on an EMPTY readline so every other member keeps the pipe
|
|
63
|
+
* path's exact semantics (auto-denied asks, no chips, no pops). */
|
|
64
|
+
function taskFileInput(content) {
|
|
65
|
+
const base = readlineInput(createInterface({ input: Readable.from([]), output: process.stdout }));
|
|
66
|
+
return {
|
|
67
|
+
...base,
|
|
68
|
+
literal: true,
|
|
69
|
+
onLine(cb) {
|
|
70
|
+
cb(content);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
57
74
|
function readlineInput(rl) {
|
|
58
75
|
let lineCb = null;
|
|
59
76
|
const pending = [];
|
|
@@ -84,6 +101,10 @@ function readlineInput(rl) {
|
|
|
84
101
|
/* readline has no ctrl+o binding — ignored (W15 rides the
|
|
85
102
|
* editor path only). */
|
|
86
103
|
},
|
|
104
|
+
onCopy() {
|
|
105
|
+
/* readline has no ctrl+x binding — ignored. `/copy` still
|
|
106
|
+
* works here: it is a typed command, not a key. */
|
|
107
|
+
},
|
|
87
108
|
question(query, cb) {
|
|
88
109
|
rl.question(query, cb);
|
|
89
110
|
},
|
|
@@ -164,6 +185,9 @@ function editorInput(editor) {
|
|
|
164
185
|
onExpand(cb) {
|
|
165
186
|
editor.onExpand(cb);
|
|
166
187
|
},
|
|
188
|
+
onCopy(cb) {
|
|
189
|
+
editor.onCopy(cb);
|
|
190
|
+
},
|
|
167
191
|
// KC2 §2: the redirect gesture — the editor decides WHEN (the
|
|
168
192
|
// same-chunk pair, the precedence gate); chat decides what it MEANS.
|
|
169
193
|
onRedirect(cb) {
|
|
@@ -402,10 +426,26 @@ function extensionsBanner(resume = []) {
|
|
|
402
426
|
// filesystem calls it.
|
|
403
427
|
const home = homedir();
|
|
404
428
|
const cwd = process.cwd();
|
|
429
|
+
/** DC-49: realpath, falling back to the raw path when it cannot be
|
|
430
|
+
* resolved — an unresolvable path is not a match, and throwing here
|
|
431
|
+
* would take the banner down over a cosmetic row. */
|
|
432
|
+
const realOf = (dir) => {
|
|
433
|
+
try {
|
|
434
|
+
return realpathSync(dir);
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return dir;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
405
440
|
body.banner(VERSION, text.replace(/^ · /, ""), resume, {
|
|
406
441
|
model: agentModel,
|
|
407
442
|
mode: getMode() === "plan" ? "plan (read-only)" : getMode(),
|
|
408
443
|
cwd: cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd,
|
|
444
|
+
// DC-49 — REALPATH on both sides. A symlinked HOME (or a symlinked
|
|
445
|
+
// cwd) compares unequal as raw strings while being the same
|
|
446
|
+
// directory, and the row would then be absent exactly where it is
|
|
447
|
+
// most needed.
|
|
448
|
+
homeWorkspace: realOf(cwd) === realOf(home),
|
|
409
449
|
});
|
|
410
450
|
}
|
|
411
451
|
/**
|
|
@@ -578,7 +618,7 @@ async function makeAgent(sessionId, input, modelFlag) {
|
|
|
578
618
|
store,
|
|
579
619
|
// Area 5: the coding tools are bound to the workspace — every path
|
|
580
620
|
// they touch is canonicalized inside cwd, escapes are refused.
|
|
581
|
-
tools: [...createCodingTools({ workspaceRoot: process.cwd() })],
|
|
621
|
+
tools: [...createCodingTools({ workspaceRoot: process.cwd(), excludeRoots: [kisoHome()] })], // DC-49
|
|
582
622
|
// Modes: the five tiers ride the E1 policy chain (mode:<tier>
|
|
583
623
|
// extensions, current tier first) — the old static PERMISSION_POLICY
|
|
584
624
|
// is gone, its semantics live in the "default" tier. The banner
|
|
@@ -844,6 +884,25 @@ async function main() {
|
|
|
844
884
|
else {
|
|
845
885
|
setMode(modeFromEnv() ?? loadUserConfig()?.mode ?? "default");
|
|
846
886
|
}
|
|
887
|
+
// CX-1 F5: --task-file <path> — the file is the ONE user turn (the
|
|
888
|
+
// structured child entry). Read BEFORE any session exists: an
|
|
889
|
+
// unreadable file is a usage error with nothing executed.
|
|
890
|
+
let taskFile;
|
|
891
|
+
{
|
|
892
|
+
const i = args.indexOf("--task-file");
|
|
893
|
+
if (i !== -1) {
|
|
894
|
+
const path = args[i + 1];
|
|
895
|
+
if (path === undefined)
|
|
896
|
+
throw new CliUsageError("--task-file needs a path");
|
|
897
|
+
try {
|
|
898
|
+
taskFile = readFileSync(path, "utf8");
|
|
899
|
+
}
|
|
900
|
+
catch (err) {
|
|
901
|
+
throw new CliUsageError(`--task-file: cannot read ${path}: ${err.message}`);
|
|
902
|
+
}
|
|
903
|
+
args.splice(i, 2);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
847
906
|
const [command, arg] = args;
|
|
848
907
|
// round 8: faux mode is the keyless demo script — an exhausted script must
|
|
849
908
|
// exit non-zero, never masquerade as a successful provider run. The
|
|
@@ -855,7 +914,7 @@ async function main() {
|
|
|
855
914
|
// (entered here, dock-bound, trusted before any extension loads),
|
|
856
915
|
// readline elsewhere. The trust question, chat, and resume all read
|
|
857
916
|
// through it; main's finally closes it on every exit path.
|
|
858
|
-
const input = makeLineInput();
|
|
917
|
+
const input = taskFile !== undefined ? taskFileInput(taskFile) : makeLineInput();
|
|
859
918
|
// R3a — cross-session input history: ~/.kiso/history, one line per
|
|
860
919
|
// entry, appended on submit, tail-500 at load (truncated by REWRITE
|
|
861
920
|
// at startup so the file never grows unbounded). Unreadable file =
|
|
@@ -1073,7 +1132,12 @@ async function main() {
|
|
|
1073
1132
|
agent = await makeAgent(id, input, modelFlag);
|
|
1074
1133
|
// finding E4-1's faux resolution rides chatLoop (currentFaux).
|
|
1075
1134
|
faux = currentFaux;
|
|
1076
|
-
|
|
1135
|
+
// CX-1 F9 (audit F9): the bare entry resolves autoCompact from
|
|
1136
|
+
// the SAME merged config chat/resume use — the env override
|
|
1137
|
+
// lives inside the resolver. It used to pass the env-only
|
|
1138
|
+
// reader, so a user's config.json autoCompact worked on
|
|
1139
|
+
// `kiso chat` and silently vanished on the default entry.
|
|
1140
|
+
await chatLoop(agent, id, input, resolveAutoCompact(mergedConfig));
|
|
1077
1141
|
break;
|
|
1078
1142
|
}
|
|
1079
1143
|
}
|
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,
|
|
@@ -63,6 +67,9 @@ export declare function atFiles(): readonly AtItem[];
|
|
|
63
67
|
* source.
|
|
64
68
|
*/
|
|
65
69
|
export interface LineInput {
|
|
70
|
+
/** CX-1 F5: task-file mode — every line delivered is a TURN, never a
|
|
71
|
+
* command; the dispatcher is bypassed (a leading `/` is content). */
|
|
72
|
+
readonly literal?: boolean;
|
|
66
73
|
onLine(cb: (line: string) => void): void;
|
|
67
74
|
onSigint(cb: () => void): void;
|
|
68
75
|
onEot(cb: () => void): void;
|
|
@@ -70,6 +77,10 @@ export interface LineInput {
|
|
|
70
77
|
/** W15: the expand key (ctrl+o) — the chain-level action, never the
|
|
71
78
|
* editor's own interpretation. */
|
|
72
79
|
onExpand(cb: () => void): void;
|
|
80
|
+
/** E1 §3: the copy key (ctrl+x) — the chain-level action, never the
|
|
81
|
+
* composer's own. The readline input ignores it; `/copy` is the
|
|
82
|
+
* route that works everywhere. */
|
|
83
|
+
onCopy(cb: () => void): void;
|
|
73
84
|
/** KC2 §2: the redirect gesture (Alt+Enter / Ctrl+Enter) — the
|
|
74
85
|
* buffer's text arrives as a line at the same instant the run is told
|
|
75
86
|
* 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.1",
|
|
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.1",
|
|
22
|
+
"@vincemakes/kiso-core": "0.26.1",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.26.1",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.26.1",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.26.1",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.26.1",
|
|
27
|
+
"@vincemakes/kiso-runtime": "0.26.1",
|
|
28
|
+
"@vincemakes/kiso-skills-ext": "0.26.1",
|
|
29
|
+
"@vincemakes/kiso-subagent-ext": "0.26.1",
|
|
30
|
+
"@vincemakes/kiso-task-ext": "0.26.1",
|
|
31
|
+
"@vincemakes/kiso-tools-node": "0.26.1",
|
|
32
|
+
"@vincemakes/kiso-tui": "0.26.1",
|
|
33
|
+
"@vincemakes/kiso-tui-cells": "0.26.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.1.2",
|