skyloom 1.13.0 → 1.13.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/README.md +17 -22
- package/dist/cli/main.js +79 -78
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/tui.d.ts +52 -19
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +192 -266
- package/dist/cli/tui.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/main.ts +58 -66
- package/src/cli/tui.ts +220 -272
- package/tests/tui.test.ts +67 -0
package/src/cli/main.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { listProviders, modelsFor, providerLabel, validateModel } from "../core/
|
|
|
12
12
|
import { agentTheme } from "../core/theme";
|
|
13
13
|
import { classify } from "../core/router";
|
|
14
14
|
import { InteractiveMode, ModeController } from "./mode";
|
|
15
|
-
import {
|
|
15
|
+
import { readLine, renderPalette, StreamRenderer } from "./tui";
|
|
16
16
|
|
|
17
17
|
const MODE = new ModeController();
|
|
18
18
|
const VERSION = (() => { try { return require("../../package.json").version; } catch { return "1.5.2"; } })();
|
|
@@ -137,52 +137,59 @@ function formatCost(c: number): string {
|
|
|
137
137
|
async function streamResponse(agent: any, input: string): Promise<void> {
|
|
138
138
|
const theme = agentTheme(agent.name);
|
|
139
139
|
const pigment = chalk.hex(theme.hex);
|
|
140
|
+
const out = process.stdout;
|
|
141
|
+
|
|
142
|
+
// ── Thinking spinner (animates until the first token lands; TTY only) ──
|
|
143
|
+
const isTTY = !!out.isTTY;
|
|
144
|
+
const frames = ["· ", "·· ", " ··", " ·"];
|
|
145
|
+
let fi = 0, spinning = true;
|
|
146
|
+
const draw = () => { if (spinning && isTTY) out.write(`\r ${pigment(theme.symbol)} ${chalk.dim("思忖 " + frames[fi++ % frames.length])}`); };
|
|
147
|
+
const timer = isTTY ? setInterval(draw, 140) : null; draw();
|
|
148
|
+
const stopSpinner = () => { if (spinning) { spinning = false; if (timer) clearInterval(timer); if (isTTY) out.write("\r" + " ".repeat(20) + "\r"); } };
|
|
149
|
+
|
|
150
|
+
let headerShown = false;
|
|
140
151
|
let mode: "none" | "reasoning" | "content" = "none";
|
|
141
|
-
let
|
|
152
|
+
let renderer: StreamRenderer | null = null;
|
|
153
|
+
const header = () => { if (!headerShown) { out.write("\n " + chalk.bold.hex(theme.hex)(`${theme.symbol} ${theme.kanji}`) + chalk.hex(theme.hex)(` ${theme.name}`) + "\n\n"); headerShown = true; } };
|
|
154
|
+
const endBlock = () => { if (renderer) { renderer.flush(); renderer = null; out.write("\n"); } };
|
|
142
155
|
|
|
143
|
-
|
|
144
|
-
for (const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
process.stdout.write((ev.success ? chalk.green(" ✓ ") : chalk.red(" ✗ ")) + chalk.dim(String(ev.tool_name)) + "\n");
|
|
175
|
-
atLineStart = true; mode = "none";
|
|
176
|
-
break;
|
|
177
|
-
case "truncated":
|
|
178
|
-
process.stdout.write(chalk.yellow(`\n ⚠ 截断: ${ev.reason}\n`));
|
|
179
|
-
break;
|
|
180
|
-
case "done":
|
|
181
|
-
break;
|
|
156
|
+
try {
|
|
157
|
+
for await (const ev of agent.chatStream(input)) {
|
|
158
|
+
switch (ev.type) {
|
|
159
|
+
case "reasoning":
|
|
160
|
+
stopSpinner();
|
|
161
|
+
if (mode !== "reasoning") { out.write(chalk.dim(" ◦ 思考 ")); mode = "reasoning"; }
|
|
162
|
+
out.write(chalk.dim.italic(String(ev.text).replace(/\s+/g, " ")));
|
|
163
|
+
break;
|
|
164
|
+
case "content":
|
|
165
|
+
stopSpinner();
|
|
166
|
+
if (mode === "reasoning") out.write("\n");
|
|
167
|
+
if (mode !== "content") { header(); renderer = new StreamRenderer(out, { gutter: " " }); mode = "content"; }
|
|
168
|
+
renderer!.write(String(ev.text));
|
|
169
|
+
break;
|
|
170
|
+
case "tool_status":
|
|
171
|
+
stopSpinner();
|
|
172
|
+
endBlock();
|
|
173
|
+
out.write("\n " + pigment(`${theme.symbol} ${ev.tool_name}`) + (ev.label ? chalk.dim(` ${ev.label}`) : "") + chalk.dim(" …") + "\n");
|
|
174
|
+
mode = "none";
|
|
175
|
+
break;
|
|
176
|
+
case "tool_done":
|
|
177
|
+
out.write(" " + (ev.success ? chalk.hex("#3a7a6e")("✓") : chalk.hex("#b3342d")("✗")) + " " + chalk.dim(String(ev.tool_name)) + "\n");
|
|
178
|
+
mode = "none";
|
|
179
|
+
break;
|
|
180
|
+
case "truncated":
|
|
181
|
+
endBlock();
|
|
182
|
+
out.write(chalk.yellow(`\n ⚠ ${ev.reason}\n`));
|
|
183
|
+
break;
|
|
184
|
+
case "done":
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
182
187
|
}
|
|
188
|
+
} finally {
|
|
189
|
+
stopSpinner();
|
|
190
|
+
endBlock();
|
|
183
191
|
}
|
|
184
|
-
|
|
185
|
-
process.stdout.write("\n\n");
|
|
192
|
+
out.write("\n");
|
|
186
193
|
}
|
|
187
194
|
|
|
188
195
|
/* ═══════════════════════════════════════
|
|
@@ -319,15 +326,15 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
319
326
|
let currentAgent = agent; // mutable for agent switching
|
|
320
327
|
welcome(agent);
|
|
321
328
|
|
|
322
|
-
process.stdout.write(chalk.dim("
|
|
323
|
-
|
|
324
|
-
// ── TUI loop ──
|
|
325
|
-
const ctx_: TUIContext = { agent: currentAgent, agents: ctx.agentMap, model: "default", cost: "$0", width: 80, height: 24 };
|
|
329
|
+
process.stdout.write(chalk.dim(" · 输入 / 看命令(Tab 补全)· ↑↓ 翻历史 · Ctrl-C 退出\n\n"));
|
|
326
330
|
|
|
327
331
|
while (true) {
|
|
328
|
-
const inp = await
|
|
332
|
+
const inp = await readLine(currentAgent.name);
|
|
329
333
|
if (!inp) continue;
|
|
330
334
|
|
|
335
|
+
// Bare "/" → show the inline command palette
|
|
336
|
+
if (inp === "/") { process.stdout.write("\n" + renderPalette("") + "\n"); continue; }
|
|
337
|
+
|
|
331
338
|
const cmdL = inp.toLowerCase();
|
|
332
339
|
|
|
333
340
|
// Agent switch — stamp a mineral seal on change
|
|
@@ -336,7 +343,7 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
336
343
|
if (cmdL === "/" + n) {
|
|
337
344
|
const a = ctx.agentMap.get(n);
|
|
338
345
|
if (a) {
|
|
339
|
-
await a.init(); currentAgent = a;
|
|
346
|
+
await a.init(); currentAgent = a;
|
|
340
347
|
const t = agentTheme(n);
|
|
341
348
|
process.stdout.write("\n " + chalk.bold.hex(t.hex)(`▣ ${t.kanji} ${t.pigment}`) + chalk.dim(` · ${t.specialty}`) + "\n");
|
|
342
349
|
process.stdout.write(" " + chalk.dim.italic(t.poem) + "\n\n");
|
|
@@ -347,7 +354,7 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
347
354
|
if (switched) continue;
|
|
348
355
|
if (cmdL === "/quit" || cmdL === "/exit") break;
|
|
349
356
|
if (cmdL === "/clear") { console.clear(); continue; }
|
|
350
|
-
if (cmdL === "/help") { process.stdout.write(
|
|
357
|
+
if (cmdL === "/help") { process.stdout.write("\n" + renderPalette("") + "\n"); continue; }
|
|
351
358
|
if (cmdL === "/version") { process.stdout.write(" Skyloom v" + VERSION + "\n"); continue; }
|
|
352
359
|
if (cmdL === "/status") { process.stdout.write(chalk.bold("\n " + currentAgent.displayName + " (" + currentAgent.name + ")\n") + chalk.dim(" State: " + currentAgent.state + " · Memory: " + currentAgent.memory.shortTerm.length + " msgs\n\n")); continue; }
|
|
353
360
|
if (cmdL === "/cost") { process.stdout.write(chalk.bold("\n Total: " + formatCost(ctx.llm.getTotalCost()) + "\n\n")); continue; }
|
|
@@ -363,7 +370,7 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
363
370
|
if (cmdL.startsWith("/task ")) { const g = inp.slice(6); process.stdout.write(chalk.cyan("\n ✦ " + g + "\n\n")); await runTask(g); continue; }
|
|
364
371
|
if (cmdL === "/setup") { const r = await setupWizard(); if (r) process.stdout.write(chalk.green(` ${r.provider} · ${r.model} — Ready!\n`)); continue; }
|
|
365
372
|
if (cmdL.startsWith("/model")) { process.stdout.write(chalk.dim(" Run /setup to reconfigure models\n")); continue; }
|
|
366
|
-
if (inp.startsWith("/")) { process.stdout.write(
|
|
373
|
+
if (inp.startsWith("/")) { process.stdout.write("\n" + chalk.dim(` 未知命令 ${inp.split(" ")[0]}\n`) + renderPalette(cmdL.split(" ")[0]) + "\n"); continue; }
|
|
367
374
|
|
|
368
375
|
// ── Chat (real streaming) ──
|
|
369
376
|
try {
|
|
@@ -391,21 +398,6 @@ async function runTask(goal: string): Promise<void> {
|
|
|
391
398
|
await ctx.closeAll();
|
|
392
399
|
}
|
|
393
400
|
|
|
394
|
-
function helpText(): string {
|
|
395
|
-
const groups: [string, [string, string][]][] = [
|
|
396
|
-
["Agent", [["/fog /rain /frost", "Switch agents"], ["/snow /dew /fair", "Switch agents"]]],
|
|
397
|
-
["Chat", [["/help", "Commands"], ["/clear", "Clear"], ["/compact", "Compress"], ["/retry", "Resend"]]],
|
|
398
|
-
["Info", [["/status", "Status"], ["/cost", "Cost"], ["/memory", "Memory"], ["/sessions", "Sessions"], ["/workspace", "Workspace"], ["/version", "Version"]]],
|
|
399
|
-
["Orch.", [["/task <goal>", "Multi-agent"]]],
|
|
400
|
-
];
|
|
401
|
-
let s = "";
|
|
402
|
-
for (const [title, cmds] of groups) {
|
|
403
|
-
s += chalk.cyan(` ${title}\n`);
|
|
404
|
-
for (const [c, d] of cmds) s += ` ${chalk.cyan(c.padEnd(18))}${chalk.dim(d)}\n`;
|
|
405
|
-
}
|
|
406
|
-
s += "\n";
|
|
407
|
-
return s;
|
|
408
|
-
}
|
|
409
401
|
|
|
410
402
|
/* ═══════════════════════════════════════
|
|
411
403
|
Entry
|