wholestack 0.4.0 → 0.5.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/{chunk-7DJJXUV4.js → chunk-PGKDYDAR.js} +1087 -47
- package/dist/cli.js +766 -20
- package/dist/index.d.ts +101 -5
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -39,7 +39,22 @@ function renderTodos(todos) {
|
|
|
39
39
|
}
|
|
40
40
|
line();
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
var SHIMMER = [44, 44, 45, 80, 81, 117, 147, 176, 176, 147, 117, 81, 45, 44];
|
|
43
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
44
|
+
function shimmerRow(row, rowIdx, phase) {
|
|
45
|
+
let out = "";
|
|
46
|
+
for (let col = 0; col < row.length; col++) {
|
|
47
|
+
const ch = row[col];
|
|
48
|
+
if (ch === " ") {
|
|
49
|
+
out += ch;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const code = SHIMMER[(col + rowIdx + phase) % SHIMMER.length];
|
|
53
|
+
out += `\x1B[1;38;5;${code}m${ch}\x1B[0m`;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
async function banner() {
|
|
43
58
|
const art = [
|
|
44
59
|
"\u2590\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u258C",
|
|
45
60
|
"\u2590 .----------------. \u258C",
|
|
@@ -62,13 +77,55 @@ function banner() {
|
|
|
62
77
|
if (i === 8) return " " + c.dim("/build for full stack apps");
|
|
63
78
|
return "";
|
|
64
79
|
};
|
|
80
|
+
const renderFinal = () => {
|
|
81
|
+
art.forEach((row, i) => {
|
|
82
|
+
const colored = zRows.has(i) ? c.bold(c.cyan(row)) : c.cyan(row);
|
|
83
|
+
line(" " + colored + side(i));
|
|
84
|
+
});
|
|
85
|
+
};
|
|
65
86
|
line();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
const animate = TTY && useColor && !process.env.ZETA_NO_ANIM;
|
|
88
|
+
if (!animate) {
|
|
89
|
+
renderFinal();
|
|
90
|
+
line();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const FRAME_MS = 70;
|
|
94
|
+
const MAX_FRAMES = 900;
|
|
95
|
+
const stdin2 = process.stdin;
|
|
96
|
+
let stop = false;
|
|
97
|
+
const onKey = () => {
|
|
98
|
+
stop = true;
|
|
99
|
+
};
|
|
100
|
+
const rawCapable = typeof stdin2.setRawMode === "function";
|
|
101
|
+
if (rawCapable) stdin2.setRawMode(true);
|
|
102
|
+
stdin2.resume();
|
|
103
|
+
stdin2.once("data", onKey);
|
|
104
|
+
process.stdout.write("\x1B[?25l");
|
|
105
|
+
try {
|
|
106
|
+
for (let f = 0; f < MAX_FRAMES && !stop; f++) {
|
|
107
|
+
if (f > 0) process.stdout.write(`\x1B[${art.length}A`);
|
|
108
|
+
art.forEach((row, i) => {
|
|
109
|
+
process.stdout.write("\r " + shimmerRow(row, i, f) + "\x1B[K\n");
|
|
110
|
+
});
|
|
111
|
+
await sleep(FRAME_MS);
|
|
112
|
+
}
|
|
113
|
+
process.stdout.write(`\x1B[${art.length}A`);
|
|
114
|
+
art.forEach((row, i) => {
|
|
115
|
+
const colored = zRows.has(i) ? c.bold(c.cyan(row)) : c.cyan(row);
|
|
116
|
+
process.stdout.write("\r " + colored + side(i) + "\x1B[K\n");
|
|
117
|
+
});
|
|
118
|
+
} finally {
|
|
119
|
+
process.stdout.write("\x1B[?25h");
|
|
120
|
+
stdin2.removeListener("data", onKey);
|
|
121
|
+
if (rawCapable) stdin2.setRawMode(false);
|
|
122
|
+
stdin2.pause();
|
|
123
|
+
}
|
|
70
124
|
line();
|
|
71
125
|
}
|
|
126
|
+
function bell() {
|
|
127
|
+
if (TTY && !process.env.ZETA_NO_BELL) process.stderr.write("\x07");
|
|
128
|
+
}
|
|
72
129
|
function fmtTokens(n) {
|
|
73
130
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
74
131
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
@@ -112,26 +169,6 @@ function wrapPlain(text, width) {
|
|
|
112
169
|
}
|
|
113
170
|
return out;
|
|
114
171
|
}
|
|
115
|
-
function responseBox(text, title = "Zeta-G1.0") {
|
|
116
|
-
const inner = boxInnerWidth();
|
|
117
|
-
const textW = inner - 2;
|
|
118
|
-
const rows = wrapPlain(text, textW);
|
|
119
|
-
const label = ` ${title} `;
|
|
120
|
-
const rem = Math.max(0, inner - label.length);
|
|
121
|
-
const lft = Math.floor(rem / 2);
|
|
122
|
-
const top = "\u256D" + "\u2500".repeat(lft) + label + "\u2500".repeat(rem - lft) + "\u256E";
|
|
123
|
-
const blank = "\u2502" + " ".repeat(inner) + "\u2502";
|
|
124
|
-
const bottom = "\u2570" + "\u2500".repeat(inner) + "\u256F";
|
|
125
|
-
line();
|
|
126
|
-
line(" " + c.cyan(top));
|
|
127
|
-
line(" " + c.cyan(blank));
|
|
128
|
-
for (const r of rows) {
|
|
129
|
-
line(" " + c.cyan("\u2502") + " " + r.padEnd(textW) + " " + c.cyan("\u2502"));
|
|
130
|
-
}
|
|
131
|
-
line(" " + c.cyan(blank));
|
|
132
|
-
line(" " + c.cyan(bottom));
|
|
133
|
-
line();
|
|
134
|
-
}
|
|
135
172
|
function userBox(text) {
|
|
136
173
|
const inner = boxInnerWidth();
|
|
137
174
|
const textW = inner - 2;
|
|
@@ -158,23 +195,166 @@ function reasoningHeader() {
|
|
|
158
195
|
line();
|
|
159
196
|
line(" " + c.magenta("\u273B ") + c.dim(c.italic("thinking")));
|
|
160
197
|
}
|
|
161
|
-
var
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
var SWIPE = [
|
|
199
|
+
238,
|
|
200
|
+
239,
|
|
201
|
+
240,
|
|
202
|
+
242,
|
|
203
|
+
244,
|
|
204
|
+
246,
|
|
205
|
+
248,
|
|
206
|
+
250,
|
|
207
|
+
252,
|
|
208
|
+
254,
|
|
209
|
+
255,
|
|
210
|
+
87,
|
|
211
|
+
51,
|
|
212
|
+
45,
|
|
213
|
+
44,
|
|
214
|
+
45,
|
|
215
|
+
51,
|
|
216
|
+
87,
|
|
217
|
+
255,
|
|
218
|
+
254,
|
|
219
|
+
252,
|
|
220
|
+
250,
|
|
221
|
+
248,
|
|
222
|
+
246,
|
|
223
|
+
244,
|
|
224
|
+
242,
|
|
225
|
+
240,
|
|
226
|
+
239
|
|
227
|
+
];
|
|
228
|
+
var TRUECOLOR = /truecolor|24bit/i.test(process.env.COLORTERM ?? "");
|
|
229
|
+
function swipeRgb(t) {
|
|
230
|
+
const stops = [
|
|
231
|
+
[60, 64, 74],
|
|
232
|
+
[0, 200, 212],
|
|
233
|
+
[235, 242, 255],
|
|
234
|
+
[0, 200, 212],
|
|
235
|
+
[60, 64, 74]
|
|
236
|
+
];
|
|
237
|
+
const x = t % 1 * (stops.length - 1);
|
|
238
|
+
const i = Math.floor(x);
|
|
239
|
+
const f = x - i;
|
|
240
|
+
const a = stops[i];
|
|
241
|
+
const b = stops[Math.min(i + 1, stops.length - 1)];
|
|
242
|
+
return [
|
|
243
|
+
Math.round(a[0] + (b[0] - a[0]) * f),
|
|
244
|
+
Math.round(a[1] + (b[1] - a[1]) * f),
|
|
245
|
+
Math.round(a[2] + (b[2] - a[2]) * f)
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
function swipeAnsi(p) {
|
|
249
|
+
if (TRUECOLOR) {
|
|
250
|
+
const [r, g, b] = swipeRgb((p % SWIPE.length + SWIPE.length) % SWIPE.length / SWIPE.length);
|
|
251
|
+
return `\x1B[38;2;${r};${g};${b}m`;
|
|
252
|
+
}
|
|
253
|
+
return `\x1B[38;5;${SWIPE[(p % SWIPE.length + SWIPE.length) % SWIPE.length]}m`;
|
|
254
|
+
}
|
|
255
|
+
function shimmerText(text, phase) {
|
|
256
|
+
if (!useColor) return text;
|
|
257
|
+
let out = "";
|
|
258
|
+
for (let i = 0; i < text.length; i++) {
|
|
259
|
+
const ch = text[i];
|
|
260
|
+
if (ch === " ") {
|
|
261
|
+
out += ch;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
out += swipeAnsi(i + phase) + ch + "\x1B[0m";
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
var THINKING_WORDS = [
|
|
269
|
+
"thinking",
|
|
270
|
+
"cooking",
|
|
271
|
+
"vibin",
|
|
272
|
+
"whippin",
|
|
273
|
+
"stirring the pot",
|
|
274
|
+
"noodlin",
|
|
275
|
+
"scheming",
|
|
276
|
+
"wiring it up",
|
|
277
|
+
"summoning",
|
|
278
|
+
"locking in",
|
|
279
|
+
"manifesting",
|
|
280
|
+
"crunching",
|
|
281
|
+
"galaxy braining",
|
|
282
|
+
"percolating",
|
|
283
|
+
"marinating",
|
|
284
|
+
"conjuring",
|
|
285
|
+
"untangling",
|
|
286
|
+
"threading the needle",
|
|
287
|
+
"herding electrons",
|
|
288
|
+
"spelunking the stack",
|
|
289
|
+
"chiseling",
|
|
290
|
+
"brewing",
|
|
291
|
+
"plotting",
|
|
292
|
+
"tinkering",
|
|
293
|
+
"compiling thoughts",
|
|
294
|
+
"doing the thing"
|
|
295
|
+
];
|
|
296
|
+
var THINKING_WORDS_YOLO = [
|
|
297
|
+
"makin babies",
|
|
298
|
+
"stressin",
|
|
299
|
+
"sending it",
|
|
300
|
+
"full send",
|
|
301
|
+
"cooking with gas",
|
|
302
|
+
"going feral",
|
|
303
|
+
"yeetin code",
|
|
304
|
+
"gambling",
|
|
305
|
+
"raw-doggin prod",
|
|
306
|
+
"no cap buildin",
|
|
307
|
+
"speedrunnin",
|
|
308
|
+
"touchin grass",
|
|
309
|
+
"zootin",
|
|
310
|
+
"catchin a fade",
|
|
311
|
+
"standin on business"
|
|
312
|
+
];
|
|
313
|
+
function thinkingWords(yolo = false) {
|
|
314
|
+
return yolo ? [...THINKING_WORDS, ...THINKING_WORDS_YOLO] : THINKING_WORDS;
|
|
315
|
+
}
|
|
316
|
+
var Spinner = class {
|
|
317
|
+
phase = 0;
|
|
318
|
+
ticks = 0;
|
|
164
319
|
timer = null;
|
|
165
320
|
text = "";
|
|
166
|
-
|
|
321
|
+
phrases = null;
|
|
322
|
+
phraseIdx = 0;
|
|
323
|
+
hint = "";
|
|
324
|
+
startedAt = 0;
|
|
325
|
+
/**
|
|
326
|
+
* @param text fixed label (used when no `phrases`)
|
|
327
|
+
* @param opts.phrases rotate through these instead of a fixed label
|
|
328
|
+
* @param opts.hint dim suffix, e.g. "esc to interrupt"
|
|
329
|
+
*/
|
|
330
|
+
start(text, opts = {}) {
|
|
167
331
|
if (!TTY) return;
|
|
168
332
|
this.text = text;
|
|
169
|
-
this.
|
|
333
|
+
this.phrases = opts.phrases && opts.phrases.length ? opts.phrases : null;
|
|
334
|
+
this.hint = opts.hint ?? "";
|
|
335
|
+
this.phraseIdx = this.phrases ? Date.now() % this.phrases.length : 0;
|
|
336
|
+
this.startedAt = Date.now();
|
|
337
|
+
this.timer = setInterval(() => this.tick(), 80);
|
|
170
338
|
this.tick();
|
|
171
339
|
}
|
|
172
340
|
update(text) {
|
|
173
341
|
this.text = text;
|
|
174
342
|
}
|
|
343
|
+
label() {
|
|
344
|
+
if (this.phrases) return this.phrases[this.phraseIdx % this.phrases.length] + "\u2026";
|
|
345
|
+
return this.text;
|
|
346
|
+
}
|
|
175
347
|
tick() {
|
|
176
|
-
|
|
177
|
-
|
|
348
|
+
this.ticks++;
|
|
349
|
+
if (this.phrases && this.ticks % 30 === 0) {
|
|
350
|
+
this.phraseIdx = (this.phraseIdx + 1) % this.phrases.length;
|
|
351
|
+
}
|
|
352
|
+
this.phase = (this.phase + SWIPE.length - 1) % SWIPE.length;
|
|
353
|
+
const dot = swipeAnsi(this.phase) + "\u2726\x1B[0m";
|
|
354
|
+
const secs = Math.floor((Date.now() - this.startedAt) / 1e3);
|
|
355
|
+
const elapsed = secs >= 1 ? c.dim(` ${secs}s`) : "";
|
|
356
|
+
const tail2 = this.hint ? " " + c.dim(`(${this.hint})`) : "";
|
|
357
|
+
process.stderr.write(`\r ${dot} ${shimmerText(this.label(), this.phase)}${elapsed}${tail2}\x1B[K`);
|
|
178
358
|
}
|
|
179
359
|
stop() {
|
|
180
360
|
if (this.timer) {
|
|
@@ -721,10 +901,111 @@ function tail(s) {
|
|
|
721
901
|
return s.slice(-4e3);
|
|
722
902
|
}
|
|
723
903
|
|
|
904
|
+
// src/tasks.ts
|
|
905
|
+
import { spawn as spawn4 } from "child_process";
|
|
906
|
+
var MAX_OUTPUT_CHARS = 64 * 1024;
|
|
907
|
+
var TaskManager = class {
|
|
908
|
+
seq = 0;
|
|
909
|
+
map = /* @__PURE__ */ new Map();
|
|
910
|
+
/** Start a detached-from-the-REPL background command. Returns the task id. */
|
|
911
|
+
start(command, args, cwd) {
|
|
912
|
+
const id = `t${++this.seq}`;
|
|
913
|
+
const display = [command, ...args].join(" ");
|
|
914
|
+
const child = spawn4(command, args, {
|
|
915
|
+
cwd,
|
|
916
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
917
|
+
// Own process group so we can signal the whole tree on kill.
|
|
918
|
+
detached: false
|
|
919
|
+
});
|
|
920
|
+
const task = {
|
|
921
|
+
id,
|
|
922
|
+
display,
|
|
923
|
+
status: "running",
|
|
924
|
+
exitCode: null,
|
|
925
|
+
startedAt: Date.now(),
|
|
926
|
+
endedAt: null,
|
|
927
|
+
announced: false,
|
|
928
|
+
child,
|
|
929
|
+
buffer: ""
|
|
930
|
+
};
|
|
931
|
+
const append = (chunk) => {
|
|
932
|
+
task.buffer += chunk.toString();
|
|
933
|
+
if (task.buffer.length > MAX_OUTPUT_CHARS) {
|
|
934
|
+
task.buffer = task.buffer.slice(task.buffer.length - MAX_OUTPUT_CHARS);
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
child.stdout?.on("data", append);
|
|
938
|
+
child.stderr?.on("data", append);
|
|
939
|
+
child.on("error", (e) => {
|
|
940
|
+
task.buffer += `
|
|
941
|
+
[spawn error] ${e.message}
|
|
942
|
+
`;
|
|
943
|
+
task.status = "failed";
|
|
944
|
+
task.exitCode = task.exitCode ?? -1;
|
|
945
|
+
task.endedAt = Date.now();
|
|
946
|
+
});
|
|
947
|
+
child.on("close", (code) => {
|
|
948
|
+
task.exitCode = code;
|
|
949
|
+
task.status = code === 0 ? "exited" : "failed";
|
|
950
|
+
task.endedAt = Date.now();
|
|
951
|
+
});
|
|
952
|
+
this.map.set(id, task);
|
|
953
|
+
return this.toRecord(task);
|
|
954
|
+
}
|
|
955
|
+
toRecord(t) {
|
|
956
|
+
const { child: _child, buffer: _buffer, ...rec } = t;
|
|
957
|
+
return { ...rec };
|
|
958
|
+
}
|
|
959
|
+
get(id) {
|
|
960
|
+
const t = this.map.get(id);
|
|
961
|
+
if (!t) return null;
|
|
962
|
+
return { record: this.toRecord(t), output: t.buffer };
|
|
963
|
+
}
|
|
964
|
+
list() {
|
|
965
|
+
return [...this.map.values()].map((t) => this.toRecord(t));
|
|
966
|
+
}
|
|
967
|
+
/** Tasks that finished since the last call and haven't been announced yet. */
|
|
968
|
+
drainCompleted() {
|
|
969
|
+
const done = [];
|
|
970
|
+
for (const t of this.map.values()) {
|
|
971
|
+
if (t.status !== "running" && !t.announced) {
|
|
972
|
+
t.announced = true;
|
|
973
|
+
done.push(this.toRecord(t));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return done;
|
|
977
|
+
}
|
|
978
|
+
kill(id) {
|
|
979
|
+
const t = this.map.get(id);
|
|
980
|
+
if (!t) return false;
|
|
981
|
+
if (t.status === "running") {
|
|
982
|
+
try {
|
|
983
|
+
t.child.kill("SIGTERM");
|
|
984
|
+
} catch {
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
killAll() {
|
|
990
|
+
for (const t of this.map.values()) {
|
|
991
|
+
if (t.status === "running") {
|
|
992
|
+
try {
|
|
993
|
+
t.child.kill("SIGTERM");
|
|
994
|
+
} catch {
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
runtimeSeconds(t) {
|
|
1000
|
+
return Math.round(((t.endedAt ?? Date.now()) - t.startedAt) / 1e3);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
var tasks = new TaskManager();
|
|
1004
|
+
|
|
724
1005
|
// src/tools.ts
|
|
725
1006
|
import { tool } from "ai";
|
|
726
1007
|
import { z } from "zod";
|
|
727
|
-
import { spawn as
|
|
1008
|
+
import { spawn as spawn5 } from "child_process";
|
|
728
1009
|
import { readFile as readFile2, writeFile, mkdir, readdir, stat } from "fs/promises";
|
|
729
1010
|
import { resolve as resolve2, dirname as dirname2, relative, join as join4, sep as sep2 } from "path";
|
|
730
1011
|
import fg from "fast-glob";
|
|
@@ -739,7 +1020,7 @@ function inside(ctx, p) {
|
|
|
739
1020
|
}
|
|
740
1021
|
function runShell(cmd, args, opts) {
|
|
741
1022
|
return new Promise((res) => {
|
|
742
|
-
const child =
|
|
1023
|
+
const child = spawn5(cmd, args, { cwd: opts.cwd, shell: false });
|
|
743
1024
|
let out = "";
|
|
744
1025
|
const timer = setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs);
|
|
745
1026
|
const onAbort = () => child.kill("SIGKILL");
|
|
@@ -772,7 +1053,7 @@ async function search(ctx, pattern, searchPath, globPat, ignoreCase, maxResults,
|
|
|
772
1053
|
if (globPat) args.push("--glob", globPat);
|
|
773
1054
|
for (const ig of IGNORE) args.push("--glob", "!" + ig);
|
|
774
1055
|
args.push("-e", pattern, abs);
|
|
775
|
-
const child =
|
|
1056
|
+
const child = spawn5("rg", args, { cwd: ctx.cwd });
|
|
776
1057
|
const onAbort = () => child.kill("SIGKILL");
|
|
777
1058
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
778
1059
|
let out = "";
|
|
@@ -1237,19 +1518,77 @@ function buildTools(ctx) {
|
|
|
1237
1518
|
}
|
|
1238
1519
|
return { ok: false, error: r.error, log: r.log };
|
|
1239
1520
|
}
|
|
1521
|
+
}),
|
|
1522
|
+
run_background: tool({
|
|
1523
|
+
description: "Start a long-running command in the BACKGROUND (builds, test watchers, long scripts) and keep working. Returns a task id immediately \u2014 DO NOT wait. Poll it later with task_output, or just continue; finished tasks are reported back automatically. Use this instead of run_command when a job takes more than a few seconds or should run while you do other work. Gated by the permission layer like run_command.",
|
|
1524
|
+
inputSchema: z.object({
|
|
1525
|
+
command: z.string().describe("The executable, e.g. pnpm, npm, forge, node, vitest."),
|
|
1526
|
+
args: z.array(z.string()).default([])
|
|
1527
|
+
}),
|
|
1528
|
+
execute: async ({ command, args }) => {
|
|
1529
|
+
const display = [command, ...args].join(" ");
|
|
1530
|
+
if (await permissions.requestCommand(`(background) ${display}`) === "deny") {
|
|
1531
|
+
return {
|
|
1532
|
+
ok: false,
|
|
1533
|
+
declined: true,
|
|
1534
|
+
error: permissions.isPlan() ? permissions.planRefusal() : `declined: \`${display}\` \u2014 approve it, switch /mode, or re-run with --yes.`
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const rec = tasks.start(command, args, ctx.cwd);
|
|
1538
|
+
toolLine("run_background", c.dim(`${rec.id} \xB7 ${display}`));
|
|
1539
|
+
return {
|
|
1540
|
+
ok: true,
|
|
1541
|
+
taskId: rec.id,
|
|
1542
|
+
status: rec.status,
|
|
1543
|
+
note: `started in background as ${rec.id}; poll with task_output or keep working`
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
}),
|
|
1547
|
+
task_output: tool({
|
|
1548
|
+
description: "Check a background task started with run_background: its status (running|exited|failed), exit code, runtime, and latest output. Pass no id to list all tasks. Use after starting a background job to see whether it finished and what it produced.",
|
|
1549
|
+
inputSchema: z.object({
|
|
1550
|
+
id: z.string().optional().describe("Task id (e.g. t1). Omit to list every task."),
|
|
1551
|
+
tail: z.number().int().min(200).max(2e4).default(4e3).describe("Max output chars to return.")
|
|
1552
|
+
}),
|
|
1553
|
+
execute: async ({ id, tail: tail2 }) => {
|
|
1554
|
+
if (!id) {
|
|
1555
|
+
return {
|
|
1556
|
+
ok: true,
|
|
1557
|
+
tasks: tasks.list().map((t) => ({
|
|
1558
|
+
id: t.id,
|
|
1559
|
+
status: t.status,
|
|
1560
|
+
exitCode: t.exitCode,
|
|
1561
|
+
seconds: tasks.runtimeSeconds(t),
|
|
1562
|
+
command: t.display
|
|
1563
|
+
}))
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
const found = tasks.get(id);
|
|
1567
|
+
if (!found) return { ok: false, error: `no task ${id}` };
|
|
1568
|
+
toolLine("task_output", c.dim(`${id} \xB7 ${found.record.status}`));
|
|
1569
|
+
return {
|
|
1570
|
+
ok: true,
|
|
1571
|
+
id,
|
|
1572
|
+
status: found.record.status,
|
|
1573
|
+
exitCode: found.record.exitCode,
|
|
1574
|
+
seconds: tasks.runtimeSeconds(found.record),
|
|
1575
|
+
command: found.record.display,
|
|
1576
|
+
output: found.output.slice(-tail2)
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1240
1579
|
})
|
|
1241
1580
|
};
|
|
1242
1581
|
}
|
|
1243
1582
|
|
|
1244
1583
|
// src/hooks.ts
|
|
1245
|
-
import { spawn as
|
|
1584
|
+
import { spawn as spawn6 } from "child_process";
|
|
1246
1585
|
import { readFileSync, existsSync as existsSync4 } from "fs";
|
|
1247
1586
|
import { homedir } from "os";
|
|
1248
1587
|
import { join as join5 } from "path";
|
|
1249
1588
|
var HOOK_TIMEOUT_MS = 15e3;
|
|
1250
1589
|
function runOne(def, event, payload, cwd) {
|
|
1251
1590
|
return new Promise((res) => {
|
|
1252
|
-
const child =
|
|
1591
|
+
const child = spawn6("sh", ["-c", def.command], { cwd });
|
|
1253
1592
|
let stdout2 = "";
|
|
1254
1593
|
let stderr = "";
|
|
1255
1594
|
const timer = setTimeout(() => child.kill("SIGKILL"), HOOK_TIMEOUT_MS);
|
|
@@ -1472,10 +1811,14 @@ var OPENROUTER_URL = "https://openrouter.ai/api/v1";
|
|
|
1472
1811
|
var CEREBRAS_KEY = "CEREBRAS_API_KEY";
|
|
1473
1812
|
var KEY_ENV = "OPENROUTER_API_KEY";
|
|
1474
1813
|
var DEFAULT_CUSTOM_MODEL = "anthropic/claude-sonnet-4.6";
|
|
1814
|
+
var VISION_MODEL = process.env.ZETA_VISION_MODEL?.trim() || "anthropic/claude-sonnet-4.6";
|
|
1475
1815
|
var MODELS = /* @__PURE__ */ new Map([
|
|
1476
1816
|
["zeta-g1-lite", { modelId: "gpt-oss-120b", label: "Zeta-G1.0 Lite", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }],
|
|
1477
1817
|
["zeta-g1", { modelId: "zai-glm-4.7", label: "Zeta-G1.0", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }],
|
|
1478
|
-
["zeta-g1-max", { modelId: "zai-glm-4.7", label: "Zeta-G1.0 MAX", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }]
|
|
1818
|
+
["zeta-g1-max", { modelId: "zai-glm-4.7", label: "Zeta-G1.0 MAX", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }],
|
|
1819
|
+
// The vision tier — accepts image input (screenshots, mocks, diagrams). Routed
|
|
1820
|
+
// over OpenRouter because the Cerebras tiers are text-only.
|
|
1821
|
+
["zeta-g1-vision", { modelId: VISION_MODEL, label: "Zeta-G1.0 Vision", keyEnv: KEY_ENV, baseURL: OPENROUTER_URL, contextWindow: 2e5, thinking: null }]
|
|
1479
1822
|
]);
|
|
1480
1823
|
var MODEL_KEYS = [...MODELS.keys()];
|
|
1481
1824
|
function registerCustom(id) {
|
|
@@ -1510,6 +1853,9 @@ function resolveModelKey(raw) {
|
|
|
1510
1853
|
lite: "zeta-g1-lite",
|
|
1511
1854
|
max: "zeta-g1-max",
|
|
1512
1855
|
pro: "zeta-g1-max",
|
|
1856
|
+
vision: "zeta-g1-vision",
|
|
1857
|
+
vis: "zeta-g1-vision",
|
|
1858
|
+
image: "zeta-g1-vision",
|
|
1513
1859
|
// legacy convenience — resolve silently, but only the Zeta label is shown
|
|
1514
1860
|
opus: "zeta-g1-max",
|
|
1515
1861
|
sonnet: "zeta-g1",
|
|
@@ -1531,6 +1877,9 @@ function modelLabel(key) {
|
|
|
1531
1877
|
function modelId(key) {
|
|
1532
1878
|
return spec(key).modelId;
|
|
1533
1879
|
}
|
|
1880
|
+
function visionCapable(key) {
|
|
1881
|
+
return key === "zeta-g1-vision" || key.startsWith("custom:");
|
|
1882
|
+
}
|
|
1534
1883
|
function modelContextWindow(key) {
|
|
1535
1884
|
return spec(key).contextWindow;
|
|
1536
1885
|
}
|
|
@@ -1747,6 +2096,35 @@ var ROLE_PLANNER = [
|
|
|
1747
2096
|
"name the files and seams involved, and call out unknowns and risks up front.",
|
|
1748
2097
|
"Propose the smallest plan that fully covers the goal."
|
|
1749
2098
|
].join("\n");
|
|
2099
|
+
var ROLE_NZT_48 = [
|
|
2100
|
+
"ROLE \u2014 NZT-48 (web3 special operations):",
|
|
2101
|
+
"You are NZT-48 for this session: a web3 special-ops agent \u2014 sharper than an",
|
|
2102
|
+
"arrow, precise and tactical with every move. Voice: precise, tactical,",
|
|
2103
|
+
"economical. State the objective, the move, and the evidence \u2014 nothing else.",
|
|
2104
|
+
"",
|
|
2105
|
+
"Drive the CLI's web3 commands (audit / prove / verify) and the existing",
|
|
2106
|
+
"Foundry \xB7 Slither \xB7 Halmos \xB7 firewall harness \u2014 do not reinvent the prover.",
|
|
2107
|
+
"",
|
|
2108
|
+
"FOUR MISSIONS \u2014 run the one the request calls for, end to end:",
|
|
2109
|
+
"1. AUDIT & PROVE \u2014 discover every contract and ALL candidate invariants (never",
|
|
2110
|
+
" just the first); prove each with the real harness; fold verdicts honestly",
|
|
2111
|
+
" (proven / refuted / degraded); emit the signed bundle. Degraded is NOT a",
|
|
2112
|
+
" pass.",
|
|
2113
|
+
"2. EXPLOIT RECON (defensive) \u2014 map the attack surface (reentrancy, access",
|
|
2114
|
+
" control, overflow, oracle/price manipulation, unchecked calls, value",
|
|
2115
|
+
" extraction, proxy traps). Report severity + location + exploit path + fix.",
|
|
2116
|
+
" Hunt weaknesses to CLOSE them; never weaponize.",
|
|
2117
|
+
"3. BUILD & SHIP \u2014 idea \u2192 ISL \u2192 Solidity \u2192 proven \u2192 deploy-ready, inside the",
|
|
2118
|
+
" deterministic provable fragment. Run forge build/test, Slither, Halmos.",
|
|
2119
|
+
" Never weaken a gate to force green \u2014 self-heal the spec instead.",
|
|
2120
|
+
"4. MONITOR & RESPOND \u2014 re-prove only changed contracts on .sol PRs; report",
|
|
2121
|
+
" SHIP / NO_SHIP; surface anomalies with evidence, never act destructively.",
|
|
2122
|
+
"",
|
|
2123
|
+
"RULES OF ENGAGEMENT: truth over optics (a refuted invariant honestly reported",
|
|
2124
|
+
"is a win; fake-success is the one unforgivable error). One contract per proof",
|
|
2125
|
+
"file; honor non-vacuity. Defensive only \u2014 refuse mass targeting, live attacks,",
|
|
2126
|
+
"or anything outside an authorized engagement."
|
|
2127
|
+
].join("\n");
|
|
1750
2128
|
var DEFAULT_REGISTRY = (() => {
|
|
1751
2129
|
const r = new PromptRegistry();
|
|
1752
2130
|
r.registerSystem("zeta-g", ZETA_G_SYSTEM);
|
|
@@ -1754,6 +2132,7 @@ var DEFAULT_REGISTRY = (() => {
|
|
|
1754
2132
|
r.registerOverlay({ id: "verify", body: VERIFY_OVERLAY });
|
|
1755
2133
|
r.registerRole({ role: "coder", body: ROLE_CODER });
|
|
1756
2134
|
r.registerRole({ role: "planner", body: ROLE_PLANNER });
|
|
2135
|
+
r.registerRole({ role: "nzt-48", body: ROLE_NZT_48 });
|
|
1757
2136
|
return r;
|
|
1758
2137
|
})();
|
|
1759
2138
|
|
|
@@ -2377,6 +2756,208 @@ import {
|
|
|
2377
2756
|
streamText,
|
|
2378
2757
|
stepCountIs
|
|
2379
2758
|
} from "ai";
|
|
2759
|
+
|
|
2760
|
+
// src/markdown.ts
|
|
2761
|
+
var useColor2 = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2762
|
+
var truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? "");
|
|
2763
|
+
function fg2(r, g, b, c256) {
|
|
2764
|
+
if (!useColor2) return "";
|
|
2765
|
+
return truecolor ? `\x1B[38;2;${r};${g};${b}m` : `\x1B[38;5;${c256}m`;
|
|
2766
|
+
}
|
|
2767
|
+
var RESET = useColor2 ? "\x1B[0m" : "";
|
|
2768
|
+
var SY = {
|
|
2769
|
+
keyword: fg2(198, 120, 221, 176),
|
|
2770
|
+
// violet
|
|
2771
|
+
string: fg2(152, 195, 121, 114),
|
|
2772
|
+
// green
|
|
2773
|
+
number: fg2(209, 154, 102, 173),
|
|
2774
|
+
// orange
|
|
2775
|
+
comment: fg2(106, 115, 125, 244),
|
|
2776
|
+
// gray
|
|
2777
|
+
fn: fg2(97, 175, 239, 75),
|
|
2778
|
+
// blue
|
|
2779
|
+
punct: fg2(160, 168, 180, 247),
|
|
2780
|
+
type: fg2(229, 192, 123, 180)
|
|
2781
|
+
// yellow
|
|
2782
|
+
};
|
|
2783
|
+
var KEYWORDS = /\b(const|let|var|function|return|if|else|for|while|switch|case|break|continue|new|class|extends|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|this|super|yield|static|public|private|protected|readonly|interface|type|enum|namespace|implements|as|void|null|undefined|true|false|def|elif|lambda|pass|with|fn|let|mut|pub|use|struct|impl|match|where|select|insert|update|delete|create|table|primary|key|foreign|references|contract|mapping|address|uint256|require|emit|modifier|memory|storage|payable)\b/g;
|
|
2784
|
+
function highlightLine(src, _lang) {
|
|
2785
|
+
if (!useColor2) return src;
|
|
2786
|
+
const cm = src.match(/^(\s*)(\/\/.*|#.*)$/);
|
|
2787
|
+
if (cm) return cm[1] + SY.comment + cm[2] + RESET;
|
|
2788
|
+
let out = "";
|
|
2789
|
+
let i = 0;
|
|
2790
|
+
while (i < src.length) {
|
|
2791
|
+
const ch = src[i];
|
|
2792
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
2793
|
+
let j = i + 1;
|
|
2794
|
+
while (j < src.length && src[j] !== ch) {
|
|
2795
|
+
if (src[j] === "\\") j++;
|
|
2796
|
+
j++;
|
|
2797
|
+
}
|
|
2798
|
+
out += SY.string + src.slice(i, Math.min(j + 1, src.length)) + RESET;
|
|
2799
|
+
i = j + 1;
|
|
2800
|
+
continue;
|
|
2801
|
+
}
|
|
2802
|
+
if (/[0-9]/.test(ch) && (i === 0 || /[^A-Za-z0-9_]/.test(src[i - 1]))) {
|
|
2803
|
+
let j = i;
|
|
2804
|
+
while (j < src.length && /[0-9a-fx._]/i.test(src[j])) j++;
|
|
2805
|
+
out += SY.number + src.slice(i, j) + RESET;
|
|
2806
|
+
i = j;
|
|
2807
|
+
continue;
|
|
2808
|
+
}
|
|
2809
|
+
if (/[A-Za-z_$]/.test(ch)) {
|
|
2810
|
+
let j = i;
|
|
2811
|
+
while (j < src.length && /[A-Za-z0-9_$]/.test(src[j])) j++;
|
|
2812
|
+
const word = src.slice(i, j);
|
|
2813
|
+
KEYWORDS.lastIndex = 0;
|
|
2814
|
+
if (KEYWORDS.test(word)) out += SY.keyword + word + RESET;
|
|
2815
|
+
else if (src[j] === "(") out += SY.fn + word + RESET;
|
|
2816
|
+
else if (/^[A-Z]/.test(word)) out += SY.type + word + RESET;
|
|
2817
|
+
else out += word;
|
|
2818
|
+
i = j;
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
out += ch;
|
|
2822
|
+
i++;
|
|
2823
|
+
}
|
|
2824
|
+
return out;
|
|
2825
|
+
}
|
|
2826
|
+
function inline(s) {
|
|
2827
|
+
if (!useColor2) return s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
2828
|
+
let out = s;
|
|
2829
|
+
out = out.replace(/`([^`]+)`/g, (_m, code) => fg2(229, 192, 123, 180) + code + RESET);
|
|
2830
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, (_m, t) => "\x1B[1m" + t + "\x1B[22m");
|
|
2831
|
+
out = out.replace(/(^|[^*])\*([^*]+)\*/g, (_m, p, t) => p + "\x1B[3m" + t + "\x1B[23m");
|
|
2832
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, u) => "\x1B[4m" + t + "\x1B[24m" + c.dim(` (${u})`));
|
|
2833
|
+
return out;
|
|
2834
|
+
}
|
|
2835
|
+
function renderMarkdown(md, width = 76) {
|
|
2836
|
+
const lines = md.replace(/\r/g, "").split("\n");
|
|
2837
|
+
const out = [];
|
|
2838
|
+
let inCode = false;
|
|
2839
|
+
let codeLang = "";
|
|
2840
|
+
for (let li = 0; li < lines.length; li++) {
|
|
2841
|
+
const raw = lines[li];
|
|
2842
|
+
if (!inCode && raw.includes("|") && li + 1 < lines.length && /^\s*\|?[\s:|-]*-{2,}[\s:|-]*\|?\s*$/.test(lines[li + 1]) && lines[li + 1].includes("-")) {
|
|
2843
|
+
const header = splitTableRow(raw);
|
|
2844
|
+
li++;
|
|
2845
|
+
const body = [];
|
|
2846
|
+
while (li + 1 < lines.length && lines[li + 1].includes("|") && lines[li + 1].trim()) {
|
|
2847
|
+
li++;
|
|
2848
|
+
body.push(splitTableRow(lines[li]));
|
|
2849
|
+
}
|
|
2850
|
+
for (const tl of renderTable(header, body, width)) out.push(tl);
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
const fence = raw.match(/^\s*```(\w*)\s*$/);
|
|
2854
|
+
if (fence) {
|
|
2855
|
+
if (!inCode) {
|
|
2856
|
+
inCode = true;
|
|
2857
|
+
codeLang = fence[1] || "";
|
|
2858
|
+
out.push(c.dim(" \u250C" + (codeLang ? ` ${codeLang} ` : "\u2500").padEnd(Math.min(width, 40), "\u2500")));
|
|
2859
|
+
} else {
|
|
2860
|
+
inCode = false;
|
|
2861
|
+
out.push(c.dim(" \u2514" + "\u2500".repeat(Math.min(width, 38))));
|
|
2862
|
+
}
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
if (inCode) {
|
|
2866
|
+
out.push(c.dim(" \u2502 ") + highlightLine(raw, codeLang));
|
|
2867
|
+
continue;
|
|
2868
|
+
}
|
|
2869
|
+
const h = raw.match(/^(#{1,6})\s+(.*)$/);
|
|
2870
|
+
if (h) {
|
|
2871
|
+
const txt = inline(h[2]);
|
|
2872
|
+
out.push("");
|
|
2873
|
+
out.push(" " + (useColor2 ? "\x1B[1m" : "") + (h[1].length <= 2 ? c.cyan(txt) : txt) + RESET);
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
if (/^\s*([-*_])\1{2,}\s*$/.test(raw)) {
|
|
2877
|
+
out.push(" " + c.dim("\u2500".repeat(Math.min(width, 40))));
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
const q = raw.match(/^\s*>\s?(.*)$/);
|
|
2881
|
+
if (q) {
|
|
2882
|
+
out.push(" " + c.dim("\u258F ") + c.italic(inline(q[1])));
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
const ul = raw.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
2886
|
+
const ol = raw.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
2887
|
+
if (ul) {
|
|
2888
|
+
out.push(" " + ul[1] + c.cyan("\u2022 ") + inline(ul[2]));
|
|
2889
|
+
continue;
|
|
2890
|
+
}
|
|
2891
|
+
if (ol) {
|
|
2892
|
+
out.push(" " + ol[1] + c.cyan(ol[2] + ". ") + inline(ol[3]));
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
if (!raw.trim()) {
|
|
2896
|
+
out.push("");
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
for (const w of wrap2(inline(raw), width)) out.push(" " + w);
|
|
2900
|
+
}
|
|
2901
|
+
return out;
|
|
2902
|
+
}
|
|
2903
|
+
function vlen(s) {
|
|
2904
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
2905
|
+
}
|
|
2906
|
+
function splitTableRow(row) {
|
|
2907
|
+
let s = row.trim();
|
|
2908
|
+
if (s.startsWith("|")) s = s.slice(1);
|
|
2909
|
+
if (s.endsWith("|")) s = s.slice(0, -1);
|
|
2910
|
+
return s.split("|").map((cell) => inline(cell.trim()));
|
|
2911
|
+
}
|
|
2912
|
+
function renderTable(header, body, width) {
|
|
2913
|
+
const cols = Math.max(header.length, ...body.map((r) => r.length));
|
|
2914
|
+
const rows = [header, ...body].map((r) => {
|
|
2915
|
+
const cp = r.slice();
|
|
2916
|
+
while (cp.length < cols) cp.push("");
|
|
2917
|
+
return cp;
|
|
2918
|
+
});
|
|
2919
|
+
let w = Array.from({ length: cols }, (_, ci) => Math.max(...rows.map((r) => vlen(r[ci]))));
|
|
2920
|
+
const overhead = cols * 3 + 1;
|
|
2921
|
+
const budget = Math.max(cols * 3, width - overhead);
|
|
2922
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
2923
|
+
if (total > budget) w = w.map((x) => Math.max(3, Math.floor(x / total * budget)));
|
|
2924
|
+
const pad = (s, n) => {
|
|
2925
|
+
const v = vlen(s);
|
|
2926
|
+
if (v > n) {
|
|
2927
|
+
const plain = s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2928
|
+
return plain.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
2929
|
+
}
|
|
2930
|
+
return s + " ".repeat(n - v);
|
|
2931
|
+
};
|
|
2932
|
+
const bar = (l, m, r) => c.dim(" " + l + w.map((n) => "\u2500".repeat(n + 2)).join(m) + r);
|
|
2933
|
+
const rowLine = (r, head) => " " + c.dim("\u2502") + r.map((cell, ci) => " " + (head ? c.bold(c.cyan(pad(cell, w[ci]))) : pad(cell, w[ci])) + " ").join(c.dim("\u2502")) + c.dim("\u2502");
|
|
2934
|
+
const out = [bar("\u250C", "\u252C", "\u2510"), rowLine(rows[0], true), bar("\u251C", "\u253C", "\u2524")];
|
|
2935
|
+
for (const r of rows.slice(1)) out.push(rowLine(r, false));
|
|
2936
|
+
out.push(bar("\u2514", "\u2534", "\u2518"));
|
|
2937
|
+
return out;
|
|
2938
|
+
}
|
|
2939
|
+
function wrap2(s, width) {
|
|
2940
|
+
const words = s.split(/(\s+)/);
|
|
2941
|
+
const rows = [];
|
|
2942
|
+
let cur = "";
|
|
2943
|
+
let len = 0;
|
|
2944
|
+
const vlen2 = (x) => x.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
2945
|
+
for (const w of words) {
|
|
2946
|
+
const wl = vlen2(w);
|
|
2947
|
+
if (len + wl > width && cur) {
|
|
2948
|
+
rows.push(cur);
|
|
2949
|
+
cur = w.trimStart();
|
|
2950
|
+
len = vlen2(cur);
|
|
2951
|
+
} else {
|
|
2952
|
+
cur += w;
|
|
2953
|
+
len += wl;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
if (cur.trim()) rows.push(cur);
|
|
2957
|
+
return rows.length ? rows : [""];
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/agent.ts
|
|
2380
2961
|
var BASE_SYSTEM = `You are Zeta-G, a terminal coding agent for the ZETA engine.
|
|
2381
2962
|
|
|
2382
2963
|
ZETA turns a plain-language idea into a real, verified application: it writes
|
|
@@ -2528,6 +3109,10 @@ var Agent = class {
|
|
|
2528
3109
|
replaceHistory(messages) {
|
|
2529
3110
|
this.history = messages;
|
|
2530
3111
|
}
|
|
3112
|
+
/** A shallow copy of the current message history (for /export, inspection). */
|
|
3113
|
+
snapshot() {
|
|
3114
|
+
return [...this.history];
|
|
3115
|
+
}
|
|
2531
3116
|
/** % of the model's context window the current history occupies. */
|
|
2532
3117
|
contextPct() {
|
|
2533
3118
|
return Math.min(100, estimateTokens(this.history) / this.contextWindow * 100);
|
|
@@ -2536,7 +3121,7 @@ var Agent = class {
|
|
|
2536
3121
|
const native = this.toolNames.filter((t) => !isMcpTool(t));
|
|
2537
3122
|
const mcp = this.toolNames.filter((t) => isMcpTool(t));
|
|
2538
3123
|
const blocks = [
|
|
2539
|
-
DEFAULT_REGISTRY.composeSystem({ id: "zeta-g", overlayIds: [] }),
|
|
3124
|
+
DEFAULT_REGISTRY.composeSystem({ id: "zeta-g", role: this.opts.persona, overlayIds: [] }),
|
|
2540
3125
|
this.policy.authorityPreamble(),
|
|
2541
3126
|
`Tools available to you: ${native.join(", ")}.`
|
|
2542
3127
|
];
|
|
@@ -2582,7 +3167,7 @@ ${this.opts.memoryText}`);
|
|
|
2582
3167
|
return false;
|
|
2583
3168
|
}
|
|
2584
3169
|
/** Run one user turn end-to-end; streams to stdout, updates history. */
|
|
2585
|
-
async send(userInput, signal) {
|
|
3170
|
+
async send(userInput, signal, images) {
|
|
2586
3171
|
let input = userInput;
|
|
2587
3172
|
if (this.opts.hooks?.has("UserPromptSubmit")) {
|
|
2588
3173
|
const outcome = await this.opts.hooks.run("UserPromptSubmit", { prompt: userInput });
|
|
@@ -2598,11 +3183,22 @@ ${outcome.context.join("\n")}`;
|
|
|
2598
3183
|
}
|
|
2599
3184
|
}
|
|
2600
3185
|
this.session?.appendUser(userInput);
|
|
2601
|
-
|
|
3186
|
+
if (images && images.length > 0) {
|
|
3187
|
+
this.history.push({
|
|
3188
|
+
role: "user",
|
|
3189
|
+
content: [
|
|
3190
|
+
{ type: "text", text: input },
|
|
3191
|
+
...images.map((img) => ({ type: "image", image: img.dataUrl }))
|
|
3192
|
+
]
|
|
3193
|
+
});
|
|
3194
|
+
} else {
|
|
3195
|
+
this.history.push({ role: "user", content: input });
|
|
3196
|
+
}
|
|
2602
3197
|
await this.maybeCompact();
|
|
2603
3198
|
const providerOptions = buildProviderOptions(this.modelKey, this.thinking);
|
|
3199
|
+
const turnStart = Date.now();
|
|
2604
3200
|
const spinner = new Spinner();
|
|
2605
|
-
spinner.start("
|
|
3201
|
+
spinner.start("", { phrases: thinkingWords(this.opts.yolo), hint: "esc to interrupt" });
|
|
2606
3202
|
let spinning = true;
|
|
2607
3203
|
const stopSpin = () => {
|
|
2608
3204
|
if (spinning) {
|
|
@@ -2623,11 +3219,21 @@ ${outcome.context.join("\n")}`;
|
|
|
2623
3219
|
let aborted = false;
|
|
2624
3220
|
let textBuf = "";
|
|
2625
3221
|
let genFirstAt = 0;
|
|
3222
|
+
const toolStart = /* @__PURE__ */ new Map();
|
|
2626
3223
|
const flushText = () => {
|
|
2627
3224
|
const t = textBuf.trim();
|
|
2628
3225
|
textBuf = "";
|
|
2629
3226
|
phase = "none";
|
|
2630
|
-
if (t)
|
|
3227
|
+
if (!t) return;
|
|
3228
|
+
const inner = boxInnerWidth();
|
|
3229
|
+
const label = " Zeta-G1.0 ";
|
|
3230
|
+
const rem = Math.max(0, inner - label.length);
|
|
3231
|
+
const lft = Math.floor(rem / 2);
|
|
3232
|
+
line();
|
|
3233
|
+
line(" " + c.cyan("\u256D" + "\u2500".repeat(lft) + label + "\u2500".repeat(rem - lft) + "\u256E"));
|
|
3234
|
+
for (const ln of renderMarkdown(t, inner - 2)) line(ln);
|
|
3235
|
+
line(" " + c.cyan("\u2570" + "\u2500".repeat(inner) + "\u256F"));
|
|
3236
|
+
line();
|
|
2631
3237
|
};
|
|
2632
3238
|
try {
|
|
2633
3239
|
for await (const part of result.fullStream) {
|
|
@@ -2658,7 +3264,7 @@ ${outcome.context.join("\n")}`;
|
|
|
2658
3264
|
}
|
|
2659
3265
|
case "start-step":
|
|
2660
3266
|
if (phase !== "none" && !spinning) {
|
|
2661
|
-
spinner.start("
|
|
3267
|
+
spinner.start("", { phrases: thinkingWords(this.opts.yolo), hint: "esc to interrupt" });
|
|
2662
3268
|
spinning = true;
|
|
2663
3269
|
}
|
|
2664
3270
|
break;
|
|
@@ -2670,7 +3276,20 @@ ${outcome.context.join("\n")}`;
|
|
|
2670
3276
|
line();
|
|
2671
3277
|
phase = "none";
|
|
2672
3278
|
}
|
|
3279
|
+
{
|
|
3280
|
+
const id = part.toolCallId;
|
|
3281
|
+
if (id) toolStart.set(id, Date.now());
|
|
3282
|
+
}
|
|
3283
|
+
break;
|
|
3284
|
+
case "tool-result": {
|
|
3285
|
+
const id = part.toolCallId;
|
|
3286
|
+
const nm = String(part.toolName ?? "tool");
|
|
3287
|
+
const started = id ? toolStart.get(id) : void 0;
|
|
3288
|
+
const ms = started ? Date.now() - started : 0;
|
|
3289
|
+
if (ms >= 300) line(c.dim(` \u2713 ${nm} ${(ms / 1e3).toFixed(1)}s`));
|
|
3290
|
+
if (id) toolStart.delete(id);
|
|
2673
3291
|
break;
|
|
3292
|
+
}
|
|
2674
3293
|
case "tool-error": {
|
|
2675
3294
|
stopSpin();
|
|
2676
3295
|
if (phase === "text") flushText();
|
|
@@ -2756,6 +3375,7 @@ ${outcome.context.join("\n")}`;
|
|
|
2756
3375
|
} catch {
|
|
2757
3376
|
}
|
|
2758
3377
|
if (!aborted) await this.maybeCompact();
|
|
3378
|
+
if (!aborted && Date.now() - turnStart > 8e3) bell();
|
|
2759
3379
|
return { aborted, usage };
|
|
2760
3380
|
}
|
|
2761
3381
|
};
|
|
@@ -2773,12 +3393,14 @@ function isPermissionMode(v) {
|
|
|
2773
3393
|
}
|
|
2774
3394
|
var PLAN_REFUSAL = "refused: plan mode is read-only \u2014 present a plan for approval, don't act. The user can switch with /mode default or approve with /approve.";
|
|
2775
3395
|
var Permissions = class {
|
|
2776
|
-
constructor(mode, confirm) {
|
|
3396
|
+
constructor(mode, confirm, persistMode) {
|
|
2777
3397
|
this.mode = mode;
|
|
2778
3398
|
this.confirm = confirm;
|
|
3399
|
+
this.persistMode = persistMode;
|
|
2779
3400
|
}
|
|
2780
3401
|
mode;
|
|
2781
3402
|
confirm;
|
|
3403
|
+
persistMode;
|
|
2782
3404
|
alwaysCommands = /* @__PURE__ */ new Set();
|
|
2783
3405
|
acceptAllEdits = false;
|
|
2784
3406
|
isPlan() {
|
|
@@ -2801,8 +3423,15 @@ var Permissions = class {
|
|
|
2801
3423
|
const ans = await this.confirm(`apply this edit to ${c.bold(path)}?`, [
|
|
2802
3424
|
{ key: "y", label: "yes" },
|
|
2803
3425
|
{ key: "n", label: "no" },
|
|
2804
|
-
{ key: "a", label: "yes to all edits this session" }
|
|
3426
|
+
{ key: "a", label: "yes to all edits this session" },
|
|
3427
|
+
{ key: "r", label: "yes to all edits, every session (remember)" }
|
|
2805
3428
|
]);
|
|
3429
|
+
if (ans === "r") {
|
|
3430
|
+
this.acceptAllEdits = true;
|
|
3431
|
+
this.mode = "acceptEdits";
|
|
3432
|
+
this.persistMode?.("acceptEdits");
|
|
3433
|
+
return "allow";
|
|
3434
|
+
}
|
|
2806
3435
|
if (ans === "a") {
|
|
2807
3436
|
this.acceptAllEdits = true;
|
|
2808
3437
|
return "allow";
|
|
@@ -3023,6 +3652,139 @@ var Session = class _Session {
|
|
|
3023
3652
|
import { writeFileSync as writeFileSync2, existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
3024
3653
|
import { homedir as homedir4 } from "os";
|
|
3025
3654
|
import { join as join8 } from "path";
|
|
3655
|
+
|
|
3656
|
+
// src/git.ts
|
|
3657
|
+
import { execFileSync } from "child_process";
|
|
3658
|
+
function git(args, cwd) {
|
|
3659
|
+
return execFileSync("git", args, {
|
|
3660
|
+
cwd,
|
|
3661
|
+
encoding: "utf8",
|
|
3662
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
3663
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3664
|
+
}).trim();
|
|
3665
|
+
}
|
|
3666
|
+
function gitSafe(args, cwd) {
|
|
3667
|
+
try {
|
|
3668
|
+
return git(args, cwd);
|
|
3669
|
+
} catch {
|
|
3670
|
+
return null;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
function isRepo(cwd) {
|
|
3674
|
+
return gitSafe(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
3675
|
+
}
|
|
3676
|
+
var SECRET_HINTS = [
|
|
3677
|
+
{ re: /\bAKIA[0-9A-Z]{16}\b/, label: "AWS access key id" },
|
|
3678
|
+
{ re: /\bsk-[A-Za-z0-9]{20,}\b/, label: "OpenAI-style key" },
|
|
3679
|
+
{ re: /\bsk_live_[A-Za-z0-9]{20,}\b/, label: "Stripe live secret" },
|
|
3680
|
+
{ re: /\bghp_[A-Za-z0-9]{30,}\b/, label: "GitHub token" },
|
|
3681
|
+
{ re: /\bvbfl_[A-Za-z0-9]{8}_[A-Za-z0-9_-]{20,}\b/, label: "Wholestack PAT" },
|
|
3682
|
+
{ re: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, label: "private key" }
|
|
3683
|
+
];
|
|
3684
|
+
function scanSecrets(diff) {
|
|
3685
|
+
const hits = /* @__PURE__ */ new Set();
|
|
3686
|
+
for (const { re, label } of SECRET_HINTS) {
|
|
3687
|
+
if (re.test(diff)) hits.add(label);
|
|
3688
|
+
}
|
|
3689
|
+
return [...hits];
|
|
3690
|
+
}
|
|
3691
|
+
var MAX_DIFF_CHARS = 12e3;
|
|
3692
|
+
var gitCommands = [
|
|
3693
|
+
{
|
|
3694
|
+
name: "diff",
|
|
3695
|
+
summary: "show working changes (/diff [--staged] [path])",
|
|
3696
|
+
source: "builtin",
|
|
3697
|
+
run: (ctx) => {
|
|
3698
|
+
if (!isRepo(ctx.cwd)) {
|
|
3699
|
+
ctx.print(" " + c.yellow("not a git repository."));
|
|
3700
|
+
return { type: "handled" };
|
|
3701
|
+
}
|
|
3702
|
+
const parts = ctx.args.split(/\s+/).filter(Boolean);
|
|
3703
|
+
const staged = parts.includes("--staged") || parts.includes("--cached");
|
|
3704
|
+
const paths = parts.filter((p) => p !== "--staged" && p !== "--cached");
|
|
3705
|
+
const args = ["--no-pager", "diff"];
|
|
3706
|
+
if (staged) args.push("--staged");
|
|
3707
|
+
if (paths.length) args.push("--", ...paths);
|
|
3708
|
+
const out = gitSafe(args, ctx.cwd) ?? "";
|
|
3709
|
+
if (!out) {
|
|
3710
|
+
ctx.print(" " + c.dim(staged ? "no staged changes." : "no unstaged changes."));
|
|
3711
|
+
return { type: "handled" };
|
|
3712
|
+
}
|
|
3713
|
+
const secrets = scanSecrets(out);
|
|
3714
|
+
if (secrets.length) {
|
|
3715
|
+
ctx.print(" " + c.red(`\u26A0 possible secret(s) in diff: ${secrets.join(", ")}`));
|
|
3716
|
+
}
|
|
3717
|
+
const shown = out.length > MAX_DIFF_CHARS ? out.slice(0, MAX_DIFF_CHARS) : out;
|
|
3718
|
+
ctx.print(shown);
|
|
3719
|
+
if (out.length > MAX_DIFF_CHARS) {
|
|
3720
|
+
ctx.print(" " + c.dim(`\u2026 (${out.length - MAX_DIFF_CHARS} more chars \u2014 run \`git diff\` for the rest)`));
|
|
3721
|
+
}
|
|
3722
|
+
return { type: "handled" };
|
|
3723
|
+
}
|
|
3724
|
+
},
|
|
3725
|
+
{
|
|
3726
|
+
name: "commit",
|
|
3727
|
+
summary: "commit changes (/commit [message] \u2014 omit to auto-write one)",
|
|
3728
|
+
source: "builtin",
|
|
3729
|
+
run: (ctx) => {
|
|
3730
|
+
if (!isRepo(ctx.cwd)) {
|
|
3731
|
+
ctx.print(" " + c.yellow("not a git repository. Run `git init` first."));
|
|
3732
|
+
return { type: "handled" };
|
|
3733
|
+
}
|
|
3734
|
+
const full = (gitSafe(["--no-pager", "diff", "HEAD"], ctx.cwd) ?? "") + (gitSafe(["--no-pager", "diff", "--staged"], ctx.cwd) ?? "");
|
|
3735
|
+
const secrets = scanSecrets(full);
|
|
3736
|
+
if (secrets.length) {
|
|
3737
|
+
ctx.print(" " + c.red(`\u2717 refusing to commit \u2014 possible secret(s): ${secrets.join(", ")}`));
|
|
3738
|
+
ctx.print(" " + c.dim("remove them (or add to .gitignore) and try again."));
|
|
3739
|
+
return { type: "handled" };
|
|
3740
|
+
}
|
|
3741
|
+
const msg = ctx.args.trim();
|
|
3742
|
+
if (msg) {
|
|
3743
|
+
try {
|
|
3744
|
+
git(["add", "-A"], ctx.cwd);
|
|
3745
|
+
const out = git(["commit", "-m", msg], ctx.cwd);
|
|
3746
|
+
ctx.print(" " + c.green("\u2713 committed"));
|
|
3747
|
+
ctx.print(" " + c.dim(out.split("\n")[0] ?? ""));
|
|
3748
|
+
} catch (e) {
|
|
3749
|
+
ctx.print(" " + c.red(e.message.split("\n")[0]));
|
|
3750
|
+
}
|
|
3751
|
+
return { type: "handled" };
|
|
3752
|
+
}
|
|
3753
|
+
const branch = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"], ctx.cwd) ?? "?";
|
|
3754
|
+
return {
|
|
3755
|
+
type: "prompt",
|
|
3756
|
+
text: `Create a git commit for the current changes. Steps, using run_command:
|
|
3757
|
+
1. \`git status --porcelain\` and \`git --no-pager diff HEAD\` to see what changed.
|
|
3758
|
+
2. If nothing is staged, \`git add -A\` (but NEVER stage .env, secrets, or key files).
|
|
3759
|
+
3. Write ONE Conventional Commit message: \`type(scope): summary\` (\u226472 chars), a body only if the change needs it. Base it strictly on the actual diff \u2014 no invented changes.
|
|
3760
|
+
4. \`git commit -m "\u2026"\` (current branch: ${branch}). Show me the message you used.
|
|
3761
|
+
Do NOT push. Do NOT commit anything that looks like a credential.`
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
},
|
|
3765
|
+
{
|
|
3766
|
+
name: "pr",
|
|
3767
|
+
summary: "push the branch and open a pull request (/pr [title])",
|
|
3768
|
+
source: "builtin",
|
|
3769
|
+
run: (ctx) => {
|
|
3770
|
+
if (!isRepo(ctx.cwd)) {
|
|
3771
|
+
ctx.print(" " + c.yellow("not a git repository."));
|
|
3772
|
+
return { type: "handled" };
|
|
3773
|
+
}
|
|
3774
|
+
const branch = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"], ctx.cwd) ?? "?";
|
|
3775
|
+
const title = ctx.args.trim();
|
|
3776
|
+
return {
|
|
3777
|
+
type: "prompt",
|
|
3778
|
+
text: `Open a pull request for the current branch. Steps, using run_command:
|
|
3779
|
+
1. Confirm the branch (\`${branch}\`) is not the default; if it is, create a feature branch first.
|
|
3780
|
+
2. \`git push -u origin HEAD\`.
|
|
3781
|
+
3. \`gh pr create\` with a clear title` + (title ? ` (use: "${title}")` : " summarizing the change") + " and a concise body listing the key changes from the commit log/diff.\nIf `gh` is not installed or not authenticated, stop and tell me the manual PR URL instead."
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
];
|
|
3786
|
+
|
|
3787
|
+
// src/commands.ts
|
|
3026
3788
|
var ZETA_MD_TEMPLATE = `# ZETA.md \u2014 project memory for Zeta-G
|
|
3027
3789
|
|
|
3028
3790
|
Tell the agent how this project works. It reads this file into its system prompt.
|
|
@@ -3359,6 +4121,39 @@ var BUILTINS = [
|
|
|
3359
4121
|
ctx.print(" " + c.dim("run ") + c.cyan("zeta-g login") + c.dim(" in your shell, then restart the session."));
|
|
3360
4122
|
return { type: "handled" };
|
|
3361
4123
|
}
|
|
4124
|
+
},
|
|
4125
|
+
...gitCommands,
|
|
4126
|
+
{
|
|
4127
|
+
name: "export",
|
|
4128
|
+
aliases: ["save"],
|
|
4129
|
+
summary: "write the transcript to markdown (/export [file.md])",
|
|
4130
|
+
source: "builtin",
|
|
4131
|
+
run: (ctx) => ({ type: "export", file: ctx.args.trim() || void 0 })
|
|
4132
|
+
},
|
|
4133
|
+
{
|
|
4134
|
+
name: "tasks",
|
|
4135
|
+
aliases: ["jobs"],
|
|
4136
|
+
summary: "list background tasks (/tasks \xB7 /tasks kill <id>)",
|
|
4137
|
+
source: "builtin",
|
|
4138
|
+
run: (ctx) => {
|
|
4139
|
+
const parts = ctx.args.split(/\s+/).filter(Boolean);
|
|
4140
|
+
if (parts[0] === "kill" && parts[1]) {
|
|
4141
|
+
const ok = tasks.kill(parts[1]);
|
|
4142
|
+
ctx.print(" " + (ok ? c.green(`signaled ${parts[1]}`) : c.red(`no task ${parts[1]}`)));
|
|
4143
|
+
return { type: "handled" };
|
|
4144
|
+
}
|
|
4145
|
+
const list = tasks.list();
|
|
4146
|
+
if (list.length === 0) {
|
|
4147
|
+
ctx.print(" " + c.dim("no background tasks. The agent starts them with run_background."));
|
|
4148
|
+
return { type: "handled" };
|
|
4149
|
+
}
|
|
4150
|
+
for (const t of list) {
|
|
4151
|
+
const dot = t.status === "running" ? c.yellow("\u25CF") : t.status === "exited" ? c.green("\u2713") : c.red("\u2717");
|
|
4152
|
+
const code = t.exitCode == null ? "" : c.dim(` (exit ${t.exitCode})`);
|
|
4153
|
+
ctx.print(` ${dot} ${c.cyan(t.id)} ${c.dim(`${tasks.runtimeSeconds(t)}s`)} ${t.display}${code}`);
|
|
4154
|
+
}
|
|
4155
|
+
return { type: "handled" };
|
|
4156
|
+
}
|
|
3362
4157
|
}
|
|
3363
4158
|
];
|
|
3364
4159
|
function parseCustom(name, body, source = "custom") {
|
|
@@ -3859,8 +4654,16 @@ var InputController = class {
|
|
|
3859
4654
|
}
|
|
3860
4655
|
return [[], line2];
|
|
3861
4656
|
}
|
|
3862
|
-
/** Read one logical line
|
|
4657
|
+
/** Read one logical line. On a TTY this renders a live bordered input box
|
|
4658
|
+
* (the box appears WHILE you type, not as an after-the-fact echo). On a
|
|
4659
|
+
* non-TTY / piped stdin it falls back to plain readline. */
|
|
3863
4660
|
async readLine(promptStr) {
|
|
4661
|
+
if (stdin.isTTY && !process.env.NO_COLOR) {
|
|
4662
|
+
const text = await this.readBoxed();
|
|
4663
|
+
const trimmed2 = text.trim();
|
|
4664
|
+
if (trimmed2) this.saveHistory(trimmed2);
|
|
4665
|
+
return trimmed2;
|
|
4666
|
+
}
|
|
3864
4667
|
let acc = "";
|
|
3865
4668
|
let prompt = promptStr;
|
|
3866
4669
|
for (; ; ) {
|
|
@@ -3877,6 +4680,230 @@ var InputController = class {
|
|
|
3877
4680
|
if (trimmed) this.saveHistory(trimmed);
|
|
3878
4681
|
return trimmed;
|
|
3879
4682
|
}
|
|
4683
|
+
/**
|
|
4684
|
+
* Live bordered input editor. Redraws a 4-side box on every keystroke with
|
|
4685
|
+
* the caret tracked inside it. Supports: insert/backspace/delete, ←/→,
|
|
4686
|
+
* Home/End (Ctrl-A/E), ↑/↓ history, Tab completion (/commands + @paths),
|
|
4687
|
+
* Shift/Alt-Enter or a trailing "\\" for a newline, Enter to submit,
|
|
4688
|
+
* Ctrl-C to quit, Ctrl-U to clear, Esc to clear the line.
|
|
4689
|
+
*/
|
|
4690
|
+
readBoxed() {
|
|
4691
|
+
const PROMPT = "\u203A ";
|
|
4692
|
+
const history = loadHistory();
|
|
4693
|
+
let histIdx = history.length;
|
|
4694
|
+
let draft = "";
|
|
4695
|
+
let buf = "";
|
|
4696
|
+
let cur = 0;
|
|
4697
|
+
let prevRows = 0;
|
|
4698
|
+
let F = Math.max(8, boxInnerWidth() - 2);
|
|
4699
|
+
let W = F + 2;
|
|
4700
|
+
const up = (n) => n > 0 ? `\x1B[${n}A` : "";
|
|
4701
|
+
const right = (n) => n > 0 ? `\x1B[${n}C` : "";
|
|
4702
|
+
const layout = (s) => {
|
|
4703
|
+
const rows = [""];
|
|
4704
|
+
const map = [];
|
|
4705
|
+
let r = 0;
|
|
4706
|
+
let col = 0;
|
|
4707
|
+
for (let i = 0; i < s.length; i++) {
|
|
4708
|
+
map[i] = { r, c: col };
|
|
4709
|
+
const ch = s[i];
|
|
4710
|
+
if (ch === "\n") {
|
|
4711
|
+
r++;
|
|
4712
|
+
col = 0;
|
|
4713
|
+
rows[r] = "";
|
|
4714
|
+
continue;
|
|
4715
|
+
}
|
|
4716
|
+
rows[r] += ch;
|
|
4717
|
+
col++;
|
|
4718
|
+
if (col >= F) {
|
|
4719
|
+
r++;
|
|
4720
|
+
col = 0;
|
|
4721
|
+
rows[r] = "";
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
map[s.length] = { r, c: col };
|
|
4725
|
+
return { rows, map };
|
|
4726
|
+
};
|
|
4727
|
+
const render = () => {
|
|
4728
|
+
const S = PROMPT + buf;
|
|
4729
|
+
const { rows, map } = layout(S);
|
|
4730
|
+
const caret = map[PROMPT.length + cur];
|
|
4731
|
+
const contentRows = rows.length;
|
|
4732
|
+
let out = "";
|
|
4733
|
+
if (prevRows > 0) out += up(prevCaretR + 1) + "\r";
|
|
4734
|
+
out += "\x1B[J";
|
|
4735
|
+
out += "\r " + c.dim("\u256D" + "\u2500".repeat(W) + "\u256E") + "\n";
|
|
4736
|
+
rows.forEach((row, i) => {
|
|
4737
|
+
let cell = row;
|
|
4738
|
+
let body;
|
|
4739
|
+
if (i === 0 && cell.startsWith(PROMPT)) {
|
|
4740
|
+
body = c.cyan(PROMPT) + cell.slice(PROMPT.length).padEnd(F - PROMPT.length);
|
|
4741
|
+
} else {
|
|
4742
|
+
body = cell.padEnd(F);
|
|
4743
|
+
}
|
|
4744
|
+
out += "\r " + c.dim("\u2502") + " " + body + " " + c.dim("\u2502") + "\n";
|
|
4745
|
+
});
|
|
4746
|
+
out += "\r " + c.dim("\u2570" + "\u2500".repeat(W) + "\u256F");
|
|
4747
|
+
out += up(contentRows - caret.r) + "\r" + right(4 + caret.c);
|
|
4748
|
+
stdout.write(out);
|
|
4749
|
+
prevRows = contentRows;
|
|
4750
|
+
prevCaretR = caret.r;
|
|
4751
|
+
};
|
|
4752
|
+
let prevCaretR = 0;
|
|
4753
|
+
return new Promise((resolve3) => {
|
|
4754
|
+
emitKeypressEvents(stdin);
|
|
4755
|
+
const watching = this.activeWatch;
|
|
4756
|
+
if (watching) this.stopWatch();
|
|
4757
|
+
this.rl.pause();
|
|
4758
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
4759
|
+
stdin.resume();
|
|
4760
|
+
stdout.write("\x1B[?2004h");
|
|
4761
|
+
const onResize = () => {
|
|
4762
|
+
F = Math.max(8, boxInnerWidth() - 2);
|
|
4763
|
+
W = F + 2;
|
|
4764
|
+
prevRows = 0;
|
|
4765
|
+
render();
|
|
4766
|
+
};
|
|
4767
|
+
process.stdout.on("resize", onResize);
|
|
4768
|
+
const finish = (val) => {
|
|
4769
|
+
stdin.off("keypress", onKey);
|
|
4770
|
+
process.stdout.off("resize", onResize);
|
|
4771
|
+
stdout.write("\x1B[?2004l");
|
|
4772
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
4773
|
+
const down = prevRows - prevCaretR;
|
|
4774
|
+
stdout.write("\x1B[" + Math.max(1, down) + "B\r\n");
|
|
4775
|
+
this.rl.resume();
|
|
4776
|
+
if (watching) this.startWatch(watching);
|
|
4777
|
+
resolve3(val);
|
|
4778
|
+
};
|
|
4779
|
+
const replaceLine = (s) => {
|
|
4780
|
+
buf = s;
|
|
4781
|
+
cur = buf.length;
|
|
4782
|
+
};
|
|
4783
|
+
const onKey = (s, key) => {
|
|
4784
|
+
const name = key?.name;
|
|
4785
|
+
if (key?.ctrl && name === "c") {
|
|
4786
|
+
stdout.write("\n");
|
|
4787
|
+
this.opts.onExit();
|
|
4788
|
+
return;
|
|
4789
|
+
}
|
|
4790
|
+
if (key?.ctrl && name === "u") {
|
|
4791
|
+
buf = "";
|
|
4792
|
+
cur = 0;
|
|
4793
|
+
return render();
|
|
4794
|
+
}
|
|
4795
|
+
if (key?.ctrl && name === "w") {
|
|
4796
|
+
const before = buf.slice(0, cur).replace(/\s*\S+\s*$/, "");
|
|
4797
|
+
const after = buf.slice(cur);
|
|
4798
|
+
buf = before + after;
|
|
4799
|
+
cur = before.length;
|
|
4800
|
+
return render();
|
|
4801
|
+
}
|
|
4802
|
+
if (key?.ctrl && name === "k") {
|
|
4803
|
+
buf = buf.slice(0, cur);
|
|
4804
|
+
return render();
|
|
4805
|
+
}
|
|
4806
|
+
if (key?.meta && name === "left") {
|
|
4807
|
+
const m = buf.slice(0, cur).match(/\S+\s*$/);
|
|
4808
|
+
cur -= m ? m[0].length : cur;
|
|
4809
|
+
return render();
|
|
4810
|
+
}
|
|
4811
|
+
if (key?.meta && name === "right") {
|
|
4812
|
+
const m = buf.slice(cur).match(/^\s*\S+/);
|
|
4813
|
+
cur += m ? m[0].length : buf.length - cur;
|
|
4814
|
+
return render();
|
|
4815
|
+
}
|
|
4816
|
+
if (name === "return" || name === "enter") {
|
|
4817
|
+
if (key?.shift || key?.meta || buf.endsWith("\\")) {
|
|
4818
|
+
if (buf.endsWith("\\")) {
|
|
4819
|
+
buf = buf.slice(0, cur - 1) + "\n" + buf.slice(cur);
|
|
4820
|
+
} else {
|
|
4821
|
+
buf = buf.slice(0, cur) + "\n" + buf.slice(cur);
|
|
4822
|
+
}
|
|
4823
|
+
cur += 1;
|
|
4824
|
+
return render();
|
|
4825
|
+
}
|
|
4826
|
+
return finish(buf);
|
|
4827
|
+
}
|
|
4828
|
+
if (name === "backspace") {
|
|
4829
|
+
if (cur > 0) {
|
|
4830
|
+
buf = buf.slice(0, cur - 1) + buf.slice(cur);
|
|
4831
|
+
cur--;
|
|
4832
|
+
}
|
|
4833
|
+
return render();
|
|
4834
|
+
}
|
|
4835
|
+
if (name === "delete") {
|
|
4836
|
+
if (cur < buf.length) buf = buf.slice(0, cur) + buf.slice(cur + 1);
|
|
4837
|
+
return render();
|
|
4838
|
+
}
|
|
4839
|
+
if (name === "left") {
|
|
4840
|
+
if (cur > 0) cur--;
|
|
4841
|
+
return render();
|
|
4842
|
+
}
|
|
4843
|
+
if (name === "right") {
|
|
4844
|
+
if (cur < buf.length) cur++;
|
|
4845
|
+
return render();
|
|
4846
|
+
}
|
|
4847
|
+
if (name === "home" || key?.ctrl && name === "a") {
|
|
4848
|
+
cur = 0;
|
|
4849
|
+
return render();
|
|
4850
|
+
}
|
|
4851
|
+
if (name === "end" || key?.ctrl && name === "e") {
|
|
4852
|
+
cur = buf.length;
|
|
4853
|
+
return render();
|
|
4854
|
+
}
|
|
4855
|
+
if (name === "up") {
|
|
4856
|
+
if (histIdx === history.length) draft = buf;
|
|
4857
|
+
if (histIdx > 0) {
|
|
4858
|
+
histIdx--;
|
|
4859
|
+
replaceLine(history[histIdx]);
|
|
4860
|
+
}
|
|
4861
|
+
return render();
|
|
4862
|
+
}
|
|
4863
|
+
if (name === "down") {
|
|
4864
|
+
if (histIdx < history.length) {
|
|
4865
|
+
histIdx++;
|
|
4866
|
+
replaceLine(histIdx === history.length ? draft : history[histIdx]);
|
|
4867
|
+
}
|
|
4868
|
+
return render();
|
|
4869
|
+
}
|
|
4870
|
+
if (name === "escape") {
|
|
4871
|
+
buf = "";
|
|
4872
|
+
cur = 0;
|
|
4873
|
+
return render();
|
|
4874
|
+
}
|
|
4875
|
+
if (name === "tab") {
|
|
4876
|
+
const [hits] = this.complete(buf.slice(0, cur));
|
|
4877
|
+
if (hits.length) {
|
|
4878
|
+
const prefix = longestCommonPrefix(hits);
|
|
4879
|
+
const before = buf.slice(0, cur);
|
|
4880
|
+
const ws = Math.max(before.lastIndexOf(" "), before.lastIndexOf("\n"));
|
|
4881
|
+
const tokenStart = before.startsWith("/") && ws < 0 ? 0 : ws + 1;
|
|
4882
|
+
buf = buf.slice(0, tokenStart) + prefix + buf.slice(cur);
|
|
4883
|
+
cur = tokenStart + prefix.length;
|
|
4884
|
+
}
|
|
4885
|
+
return render();
|
|
4886
|
+
}
|
|
4887
|
+
const seq = key?.sequence ?? s ?? "";
|
|
4888
|
+
if (seq.includes("\x1B[200~") || seq.includes("\x1B[201~")) {
|
|
4889
|
+
const pasted = seq.replace(/\x1b\[20[01]~/g, "");
|
|
4890
|
+
buf = buf.slice(0, cur) + pasted + buf.slice(cur);
|
|
4891
|
+
cur += pasted.length;
|
|
4892
|
+
return render();
|
|
4893
|
+
}
|
|
4894
|
+
const ins = s && !key?.ctrl && !key?.meta ? s : "";
|
|
4895
|
+
if (ins && !ins.includes("\x1B")) {
|
|
4896
|
+
buf = buf.slice(0, cur) + ins + buf.slice(cur);
|
|
4897
|
+
cur += ins.length;
|
|
4898
|
+
return render();
|
|
4899
|
+
}
|
|
4900
|
+
};
|
|
4901
|
+
const bar = this.opts.contextBar?.();
|
|
4902
|
+
if (bar) stdout.write(" " + bar + "\n");
|
|
4903
|
+
stdin.on("keypress", onKey);
|
|
4904
|
+
render();
|
|
4905
|
+
});
|
|
4906
|
+
}
|
|
3880
4907
|
saveHistory(entry) {
|
|
3881
4908
|
try {
|
|
3882
4909
|
mkdirSync3(dirname5(HISTORY_FILE), { recursive: true });
|
|
@@ -3948,6 +4975,17 @@ var InputController = class {
|
|
|
3948
4975
|
this.rl.close();
|
|
3949
4976
|
}
|
|
3950
4977
|
};
|
|
4978
|
+
function longestCommonPrefix(items) {
|
|
4979
|
+
if (!items.length) return "";
|
|
4980
|
+
let prefix = items[0];
|
|
4981
|
+
for (const it of items.slice(1)) {
|
|
4982
|
+
let i = 0;
|
|
4983
|
+
while (i < prefix.length && i < it.length && prefix[i] === it[i]) i++;
|
|
4984
|
+
prefix = prefix.slice(0, i);
|
|
4985
|
+
if (!prefix) break;
|
|
4986
|
+
}
|
|
4987
|
+
return prefix;
|
|
4988
|
+
}
|
|
3951
4989
|
|
|
3952
4990
|
export {
|
|
3953
4991
|
c,
|
|
@@ -3961,6 +4999,7 @@ export {
|
|
|
3961
4999
|
PROPERTY_KINDS,
|
|
3962
5000
|
runProver,
|
|
3963
5001
|
killRunningApps,
|
|
5002
|
+
tasks,
|
|
3964
5003
|
buildTools,
|
|
3965
5004
|
HookRunner,
|
|
3966
5005
|
mergeHookSets,
|
|
@@ -3971,6 +5010,7 @@ export {
|
|
|
3971
5010
|
resolveModelKey,
|
|
3972
5011
|
modelLabel,
|
|
3973
5012
|
modelId,
|
|
5013
|
+
visionCapable,
|
|
3974
5014
|
modelContextWindow,
|
|
3975
5015
|
supportsThinking,
|
|
3976
5016
|
listModels,
|