skyloom 1.13.6 → 1.13.8
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/.github/workflows/ci.yml +36 -36
- package/README.md +220 -159
- package/config/providers.yaml +39 -39
- package/config/skills/api_integrator/SKILL.md +15 -15
- package/config/skills/arch_designer/SKILL.md +13 -13
- package/config/skills/ci_cd_manager/SKILL.md +14 -14
- package/config/skills/code_analysis/SKILL.md +13 -13
- package/config/skills/code_generator/SKILL.md +12 -12
- package/config/skills/code_reviewer/SKILL.md +13 -13
- package/config/skills/content_writer/SKILL.md +14 -14
- package/config/skills/data_transformer/SKILL.md +15 -15
- package/config/skills/document_analysis/SKILL.md +13 -13
- package/config/skills/emotional_companion/SKILL.md +15 -15
- package/config/skills/performance_checker/SKILL.md +14 -14
- package/config/skills/security_auditor/SKILL.md +14 -14
- package/config/skills/self_evolve/SKILL.md +13 -13
- package/config/skills/sys_operator/SKILL.md +15 -15
- package/config/skills/task_planner/SKILL.md +14 -14
- package/config/skills/web_research/SKILL.md +14 -14
- package/config/skills/workflow_designer/SKILL.md +13 -13
- package/dist/agents/dew.js +52 -52
- package/dist/agents/fair.js +84 -84
- package/dist/agents/fog.js +30 -30
- package/dist/agents/frost.js +32 -32
- package/dist/agents/rain.js +32 -32
- package/dist/agents/snow.js +68 -68
- package/dist/cli/commands_md.d.ts +41 -0
- package/dist/cli/commands_md.d.ts.map +1 -0
- package/dist/cli/commands_md.js +140 -0
- package/dist/cli/commands_md.js.map +1 -0
- package/dist/cli/input_macros.d.ts +28 -0
- package/dist/cli/input_macros.d.ts.map +1 -0
- package/dist/cli/input_macros.js +120 -0
- package/dist/cli/input_macros.js.map +1 -0
- package/dist/cli/loom.d.ts +220 -0
- package/dist/cli/loom.d.ts.map +1 -0
- package/dist/cli/loom.js +1094 -0
- package/dist/cli/loom.js.map +1 -0
- package/dist/cli/loom_chat.d.ts +20 -0
- package/dist/cli/loom_chat.d.ts.map +1 -0
- package/dist/cli/loom_chat.js +685 -0
- package/dist/cli/loom_chat.js.map +1 -0
- package/dist/cli/main.js +310 -14
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +7 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/core/agent.d.ts +20 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +199 -16
- package/dist/core/agent.js.map +1 -1
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +34 -2
- package/dist/core/factory.js.map +1 -1
- package/dist/core/file_checkpoint.d.ts +57 -0
- package/dist/core/file_checkpoint.d.ts.map +1 -0
- package/dist/core/file_checkpoint.js +162 -0
- package/dist/core/file_checkpoint.js.map +1 -0
- package/dist/core/hooks.d.ts +43 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +110 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/llm.d.ts.map +1 -1
- package/dist/core/llm.js +15 -9
- package/dist/core/llm.js.map +1 -1
- package/dist/core/longdoc.js +5 -5
- package/dist/core/mcp.d.ts +16 -0
- package/dist/core/mcp.d.ts.map +1 -1
- package/dist/core/mcp.js +55 -0
- package/dist/core/mcp.js.map +1 -1
- package/dist/core/model_config.d.ts +40 -0
- package/dist/core/model_config.d.ts.map +1 -0
- package/dist/core/model_config.js +191 -0
- package/dist/core/model_config.js.map +1 -0
- package/dist/core/skill.d.ts +7 -0
- package/dist/core/skill.d.ts.map +1 -1
- package/dist/core/skill.js +47 -0
- package/dist/core/skill.js.map +1 -1
- package/dist/core/skymd.d.ts +39 -0
- package/dist/core/skymd.d.ts.map +1 -0
- package/dist/core/skymd.js +177 -0
- package/dist/core/skymd.js.map +1 -0
- package/dist/core/tool.d.ts +12 -0
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +30 -0
- package/dist/core/tool.js.map +1 -1
- package/dist/core/verify.d.ts +27 -0
- package/dist/core/verify.d.ts.map +1 -0
- package/dist/core/verify.js +62 -0
- package/dist/core/verify.js.map +1 -0
- package/dist/skills/loader.d.ts +22 -2
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +45 -15
- package/dist/skills/loader.js.map +1 -1
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +13 -3
- package/dist/tools/builtin.js.map +1 -1
- package/dist/tools/model_tool.d.ts +11 -0
- package/dist/tools/model_tool.d.ts.map +1 -0
- package/dist/tools/model_tool.js +71 -0
- package/dist/tools/model_tool.js.map +1 -0
- package/dist/tools/todo.d.ts +30 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +78 -0
- package/dist/tools/todo.js.map +1 -0
- package/docs/AESTHETIC_DESIGN.md +152 -144
- package/docs/OPTIMIZATION_PLAN.md +178 -178
- package/package.json +68 -68
- package/scripts/install.js +48 -48
- package/scripts/link.js +10 -10
- package/setup.bat +79 -79
- package/skill-test-ty2fOA/test.md +10 -10
- package/src/agents/dew.ts +70 -70
- package/src/agents/fair.ts +102 -102
- package/src/agents/fog.ts +48 -48
- package/src/agents/frost.ts +50 -50
- package/src/agents/rain.ts +50 -50
- package/src/agents/snow.ts +239 -239
- package/src/cli/commands_md.ts +112 -0
- package/src/cli/input_macros.ts +83 -0
- package/src/cli/loom.ts +982 -0
- package/src/cli/loom_chat.ts +598 -0
- package/src/cli/main.ts +255 -9
- package/src/cli/mode.ts +58 -58
- package/src/cli/tui.ts +228 -222
- package/src/core/agent/guard.ts +134 -134
- package/src/core/agent/task.ts +100 -100
- package/src/core/agent.ts +195 -16
- package/src/core/arbitrate.ts +162 -162
- package/src/core/catalog.ts +178 -178
- package/src/core/checkpoint.ts +94 -94
- package/src/core/estimate.ts +104 -104
- package/src/core/evolve.ts +191 -191
- package/src/core/factory.ts +31 -2
- package/src/core/file_checkpoint.ts +136 -0
- package/src/core/filter.ts +103 -103
- package/src/core/graph.ts +156 -156
- package/src/core/hooks.ts +126 -0
- package/src/core/icons.ts +53 -53
- package/src/core/index.ts +37 -37
- package/src/core/learn.ts +146 -146
- package/src/core/llm.ts +15 -9
- package/src/core/longdoc.ts +155 -155
- package/src/core/mcp.ts +48 -0
- package/src/core/mcp_server.ts +176 -176
- package/src/core/model_config.ts +157 -0
- package/src/core/profile.ts +255 -255
- package/src/core/router.ts +124 -124
- package/src/core/sandbox.ts +142 -142
- package/src/core/security.ts +243 -243
- package/src/core/skill.ts +42 -0
- package/src/core/skymd.ts +143 -0
- package/src/core/theme.ts +65 -65
- package/src/core/tool.ts +30 -0
- package/src/core/tool_router.ts +193 -193
- package/src/core/vector.ts +152 -152
- package/src/core/verify.ts +71 -0
- package/src/core/workspace.ts +150 -150
- package/src/plugins/loader.ts +66 -66
- package/src/skills/loader.ts +45 -16
- package/src/sql.js.d.ts +29 -29
- package/src/tools/builtin.ts +13 -3
- package/src/tools/computer.ts +269 -269
- package/src/tools/delegate.ts +49 -49
- package/src/tools/model_tool.ts +74 -0
- package/src/tools/todo.ts +76 -0
- package/src/web/tts.ts +93 -93
- package/tests/agent.test.ts +159 -159
- package/tests/agent_helpers.test.ts +48 -48
- package/tests/bus.test.ts +121 -121
- package/tests/catalog.test.ts +86 -86
- package/tests/checkpoint_commands.test.ts +124 -0
- package/tests/claude_compat.test.ts +110 -0
- package/tests/config.test.ts +41 -41
- package/tests/guard.test.ts +75 -75
- package/tests/icons.test.ts +45 -45
- package/tests/loom.test.ts +248 -0
- package/tests/memory.test.ts +170 -170
- package/tests/model_config.test.ts +109 -0
- package/tests/router.test.ts +86 -86
- package/tests/schemas.test.ts +51 -51
- package/tests/semantic.test.ts +83 -83
- package/tests/setup.ts +10 -10
- package/tests/skill.test.ts +172 -172
- package/tests/skymd.test.ts +146 -0
- package/tests/task.test.ts +60 -60
- package/tests/todo_toolstats.test.ts +94 -0
- package/tests/tool.test.ts +108 -108
- package/tests/tool_router.test.ts +71 -71
- package/tests/tui.test.ts +67 -67
- package/vitest.config.ts +17 -17
- package/=12 +0 -0
- package/=8 +0 -0
package/src/cli/main.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { agentTheme } from "../core/theme";
|
|
|
13
13
|
import { classify } from "../core/router";
|
|
14
14
|
import { InteractiveMode, ModeController } from "./mode";
|
|
15
15
|
import { readLine, renderPalette, StreamRenderer } from "./tui";
|
|
16
|
+
import { loomChat } from "./loom_chat";
|
|
16
17
|
|
|
17
18
|
const MODE = new ModeController();
|
|
18
19
|
const VERSION = (() => { try { return require("../../package.json").version; } catch { return "1.5.2"; } })();
|
|
@@ -68,7 +69,9 @@ const program = new Command()
|
|
|
68
69
|
.name("sky").description("天空织机 Skyloom").version(VERSION);
|
|
69
70
|
|
|
70
71
|
program.command("chat").argument("[agent]", "agent name", "fog")
|
|
71
|
-
.option("-m,--model <m>", "model")
|
|
72
|
+
.option("-m,--model <m>", "model")
|
|
73
|
+
.option("--classic", "linear scrolling UI instead of the full-screen loom")
|
|
74
|
+
.action(async (a: string, o: { model?: string; classic?: boolean }) => { await chat(a, o.model, o.classic); });
|
|
72
75
|
program.command("task").argument("[goal]", "task goal")
|
|
73
76
|
.action(async (g?: string) => { if (g) await runTask(g); });
|
|
74
77
|
program.command("web").option("-p,--port <p>", "port", "3000")
|
|
@@ -186,6 +189,15 @@ async function streamResponse(agent: any, input: string): Promise<void> {
|
|
|
186
189
|
break;
|
|
187
190
|
case "tool_done":
|
|
188
191
|
out.write(" " + (ev.success ? chalk.hex("#3a7a6e")("✓") : chalk.hex("#b3342d")("✗")) + " " + chalk.dim(String(ev.tool_name)) + "\n");
|
|
192
|
+
if (ev.tool_name === "todo_write" && ev.success) {
|
|
193
|
+
try {
|
|
194
|
+
const items = agent.memory.getWorking("todos") || [];
|
|
195
|
+
if (items.length) {
|
|
196
|
+
const { renderTodoList } = require("../tools/todo");
|
|
197
|
+
out.write(chalk.dim(" ☰ " + renderTodoList(items).split("\n").join("\n ")) + "\n");
|
|
198
|
+
}
|
|
199
|
+
} catch { /* best-effort */ }
|
|
200
|
+
}
|
|
189
201
|
mode = "none";
|
|
190
202
|
break;
|
|
191
203
|
case "truncated":
|
|
@@ -294,7 +306,7 @@ async function setupWizard(): Promise<{ provider: string; key: string; model: st
|
|
|
294
306
|
return { provider: prov.id, key: key.trim(), model };
|
|
295
307
|
}
|
|
296
308
|
|
|
297
|
-
async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
309
|
+
async function chat(agentName: string, modelOverride?: string, classic?: boolean): Promise<void> {
|
|
298
310
|
const haveKey = checkApiKeys();
|
|
299
311
|
if (!haveKey) {
|
|
300
312
|
process.stdout.write("\n" + chalk.cyan(" ✦ 天空织机 Skyloom ✦\n"));
|
|
@@ -338,6 +350,18 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
338
350
|
});
|
|
339
351
|
} catch { /* security module optional */ }
|
|
340
352
|
|
|
353
|
+
// ── 立轴 (full-screen loom) is the default on a real terminal;
|
|
354
|
+
// --classic / SKYLOOM_CLASSIC=1 / pipes fall back to the linear UI. ──
|
|
355
|
+
const wantLoom =
|
|
356
|
+
!classic &&
|
|
357
|
+
!process.env.SKYLOOM_CLASSIC &&
|
|
358
|
+
!!process.stdout.isTTY && !!process.stdin.isTTY &&
|
|
359
|
+
(process.stdout.rows || 24) >= 14 && (process.stdout.columns || 80) >= 60;
|
|
360
|
+
if (wantLoom) {
|
|
361
|
+
await loomChat(ctx, agent, { version: VERSION, setupWizard, saveApiKey });
|
|
362
|
+
return; // loomChat exits the process itself
|
|
363
|
+
}
|
|
364
|
+
|
|
341
365
|
// eslint-disable-next-line prefer-const
|
|
342
366
|
let currentAgent = agent; // mutable for agent switching
|
|
343
367
|
let lastSessions: any[] = []; // index→session map for /resume <n>
|
|
@@ -346,7 +370,7 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
346
370
|
process.stdout.write(chalk.dim(" · 输入 / 看命令(Tab 补全)· ↑↓ 翻历史 · Ctrl-C 退出\n\n"));
|
|
347
371
|
|
|
348
372
|
while (true) {
|
|
349
|
-
|
|
373
|
+
let inp = await readLine(currentAgent.name);
|
|
350
374
|
if (!inp) continue;
|
|
351
375
|
|
|
352
376
|
// Bare "/" → show the inline command palette
|
|
@@ -419,11 +443,139 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
419
443
|
if (cmdL === "/mcp") { process.stdout.write(chalk.dim(" " + (ctx.mcpStatus?.join(", ") || "none") + "\n")); continue; }
|
|
420
444
|
if (cmdL.startsWith("/apikey set ")) { const p = inp.split(/\s+/); if (p.length >= 4) { saveApiKey(p[2], p[3]); process.stdout.write(chalk.green(" ✓ Saved " + p[2] + " API key\n")); } else { process.stdout.write(chalk.yellow(" Usage: /apikey set <provider> <key>\n")); } continue; }
|
|
421
445
|
if (cmdL === "/apikey") { process.stdout.write(chalk.bold("\n API Keys:\n")); for (const p of ["openai","deepseek","anthropic","groq","openrouter"]) { process.stdout.write(chalk.dim(" " + p.padEnd(14) + (!!process.env[p.toUpperCase() + "_API_KEY"] ? chalk.green("env") : chalk.dim("—")) + "\n")); } process.stdout.write("\n"); continue; }
|
|
446
|
+
if (cmdL === "/plan" || cmdL === "/auto" || cmdL === "/default") {
|
|
447
|
+
MODE.set(cmdL === "/plan" ? InteractiveMode.PLAN : cmdL === "/auto" ? InteractiveMode.AUTO : InteractiveMode.DEFAULT);
|
|
448
|
+
currentAgent.planMode = MODE.current === InteractiveMode.PLAN;
|
|
449
|
+
process.stdout.write(chalk.dim(` 模式 → ${MODE.current} · ${MODE.describe()}\n`));
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (cmdL === "/context") {
|
|
453
|
+
try {
|
|
454
|
+
const d = currentAgent.contextDetail();
|
|
455
|
+
process.stdout.write(chalk.bold(`\n 上下文 ${d.estimatedTokens}/${d.maxTokens} tokens (${d.pct}%)`) + chalk.dim(` · ${d.model}\n`));
|
|
456
|
+
process.stdout.write(chalk.dim(` 系统提示 ≈${d.systemPromptTokens} tk · 工具 ${d.toolCount} 个\n`));
|
|
457
|
+
for (const [role, v] of Object.entries(d.byRole as Record<string, { tokens: number; count: number }>)) {
|
|
458
|
+
process.stdout.write(chalk.dim(` ${role.padEnd(9)} ${String(v.tokens).padStart(6)} tk · ${v.count} 条\n`));
|
|
459
|
+
}
|
|
460
|
+
process.stdout.write("\n");
|
|
461
|
+
} catch (e: any) { process.stdout.write(chalk.dim(` 无法获取: ${e?.message || e}\n`)); }
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (cmdL === "/tools") {
|
|
465
|
+
const stats = (currentAgent as any).toolRegistry?.getStats?.() || [];
|
|
466
|
+
if (!stats.length) { process.stdout.write(chalk.dim(" 本会话当前灵还没有工具调用\n")); continue; }
|
|
467
|
+
process.stdout.write(chalk.bold(`\n 工具调用 · ${currentAgent.name}\n`));
|
|
468
|
+
for (const s of stats.slice(0, 12)) {
|
|
469
|
+
const extra = `${s.failures ? ` ✗${s.failures}` : ""}${s.cacheHits ? ` ⊙${s.cacheHits}` : ""}${s.breaker !== "closed" ? ` [熔断:${s.breaker}]` : ""}`;
|
|
470
|
+
process.stdout.write(chalk.dim(` ${s.name.padEnd(16)} ${s.calls} 次 · ${s.avgMs}ms${extra}\n`));
|
|
471
|
+
}
|
|
472
|
+
process.stdout.write("\n");
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (cmdL === "/verify") {
|
|
476
|
+
const { resolveVerifyConfig, runVerify } = require("../core/verify");
|
|
477
|
+
const vc = resolveVerifyConfig((ctx as any).config);
|
|
478
|
+
if (!vc.commands.length) { process.stdout.write(chalk.dim(" 未配置验证命令 — config.yaml verify.commands 或 SKY.md ## Verify\n")); continue; }
|
|
479
|
+
process.stdout.write(chalk.dim(` ⚙ verify · ${vc.commands.length} 条命令\n`));
|
|
480
|
+
const vr = runVerify(vc);
|
|
481
|
+
process.stdout.write(" " + vr.report.split("\n").slice(0, 30).join("\n ") + "\n");
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (cmdL === "/init") {
|
|
485
|
+
const { INIT_PROMPT } = require("../core/skymd");
|
|
486
|
+
process.stdout.write(chalk.dim(" 开始扫描项目,生成 SKY.md …\n"));
|
|
487
|
+
try { await streamResponse(currentAgent, INIT_PROMPT); currentAgent.reloadProjectMemory(); }
|
|
488
|
+
catch (e: any) { process.stdout.write(chalk.red(" ✗ " + (e.message || e) + "\n")); }
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
422
491
|
if (cmdL.startsWith("/task ")) { const g = inp.slice(6); process.stdout.write(chalk.cyan("\n ✦ " + g + "\n\n")); await runTask(g); continue; }
|
|
423
492
|
if (cmdL === "/setup") { const r = await setupWizard(); if (r) process.stdout.write(chalk.green(` ${r.provider} · ${r.model} — Ready!\n`)); continue; }
|
|
424
|
-
if (cmdL
|
|
493
|
+
if (cmdL === "/model" || cmdL.startsWith("/model ")) {
|
|
494
|
+
const { setAgentModel, setUnifiedModel, clearAgentModel, setAgentApiKey, describeAgentLLM } = require("../core/model_config");
|
|
495
|
+
const cfg = (ctx as any).config;
|
|
496
|
+
const parts = inp.split(/\s+/).slice(1);
|
|
497
|
+
if (parts.length === 0) {
|
|
498
|
+
const d = describeAgentLLM(cfg, currentAgent.name);
|
|
499
|
+
process.stdout.write(chalk.bold(`\n ${currentAgent.name} · ${d.model}`) + chalk.dim(` (${d.source === "agent" ? "独立配置" : "统一配置"} · ${d.provider || "?"} · key:${d.keySource})\n`));
|
|
500
|
+
process.stdout.write(chalk.dim(` 统一默认: ${cfg.default_model || cfg.llm?.default_model || "gpt-4o"}\n`));
|
|
501
|
+
process.stdout.write(chalk.dim(" /model <id> 单独换 · /model unified <id> 改默认 · /model reset 回统一 · /model key <key>\n\n"));
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (parts[0] === "reset") { clearAgentModel(cfg, currentAgent.name); process.stdout.write(chalk.green(` ✓ ${currentAgent.name} 已回到统一配置\n`)); continue; }
|
|
505
|
+
if (parts[0] === "unified" || parts[0] === "default") {
|
|
506
|
+
if (!parts[1]) { process.stdout.write(chalk.dim(" 用法: /model unified <模型id>\n")); continue; }
|
|
507
|
+
const r = setUnifiedModel(cfg, parts[1]);
|
|
508
|
+
process.stdout.write(r.ok ? chalk.green(` ✓ 统一默认 → ${parts[1]}\n`) : chalk.dim(` '${parts[1]}' 不在目录中${r.suggestions.length ? " · 可选: " + r.suggestions.join(", ") : ""}\n`));
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (parts[0] === "key") {
|
|
512
|
+
if (!parts[1]) { process.stdout.write(chalk.dim(" 用法: /model key <api-key>\n")); continue; }
|
|
513
|
+
setAgentApiKey(cfg, currentAgent.name, parts[1]);
|
|
514
|
+
process.stdout.write(chalk.green(` ✓ ${currentAgent.name} 的独立 API key 已保存\n`));
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const r = setAgentModel(cfg, currentAgent.name, parts[0]);
|
|
518
|
+
process.stdout.write(r.ok ? chalk.green(` ✓ ${currentAgent.name} → ${parts[0]}`) + chalk.dim(" · 下一条消息生效\n") : chalk.dim(` '${parts[0]}' 不在目录中${r.suggestions.length ? " · 可选: " + r.suggestions.join(", ") : ""}\n`));
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (cmdL === "/rewind" || cmdL.startsWith("/rewind ")) {
|
|
522
|
+
const { getFileCheckpoints } = require("../core/file_checkpoint");
|
|
523
|
+
const cp = getFileCheckpoints();
|
|
524
|
+
const arg = inp.slice(7).trim();
|
|
525
|
+
const r = cp.rewind(/^\d+$/.test(arg) ? parseInt(arg, 10) : 1);
|
|
526
|
+
if (r.turns === 0) {
|
|
527
|
+
const turns = cp.list();
|
|
528
|
+
if (!turns.length) { process.stdout.write(chalk.dim(" 没有可回退的文件改动\n")); continue; }
|
|
529
|
+
process.stdout.write(chalk.bold(` 检查点 · ${turns.length} 轮可回退\n`));
|
|
530
|
+
for (const t of turns.slice(0, 8)) process.stdout.write(chalk.dim(` ${t.label} · ${t.files.length} 个文件\n`));
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
process.stdout.write(chalk.green(` ↺ 已回退 ${r.turns} 轮`) + chalk.dim(` · 恢复 ${r.restored.length} 个文件${r.deleted.length ? ` · 删除 ${r.deleted.length} 个新建文件` : ""}\n`));
|
|
534
|
+
for (const f of [...r.restored, ...r.deleted].slice(0, 10)) process.stdout.write(chalk.dim(` ${f}\n`));
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (inp.startsWith("/")) {
|
|
538
|
+
// 自定义斜杠命令(.sky/commands/ + ~/.skyloom/commands/)
|
|
539
|
+
const { loadCustomCommands, resolveCustomCommand } = require("./commands_md");
|
|
540
|
+
const hit = resolveCustomCommand(inp, loadCustomCommands());
|
|
541
|
+
if (hit) {
|
|
542
|
+
const target = hit.command.agent ? ctx.agentMap.get(hit.command.agent) : undefined;
|
|
543
|
+
if (target) {
|
|
544
|
+
await target.init();
|
|
545
|
+
currentAgent = target;
|
|
546
|
+
}
|
|
547
|
+
process.stdout.write(chalk.dim(` ⌘ /${hit.command.name}${hit.command.agent ? ` → ${hit.command.agent}` : ""}\n`));
|
|
548
|
+
try { await streamResponse(currentAgent, hit.prompt); } catch (e: any) { process.stdout.write(chalk.red(" ✗ " + (e.message || e) + "\n")); }
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
425
552
|
if (inp.startsWith("/")) { process.stdout.write("\n" + chalk.dim(` 未知命令 ${inp.split(" ")[0]}\n`) + renderPalette(cmdL.split(" ")[0]) + "\n"); continue; }
|
|
426
553
|
|
|
554
|
+
// ── input macros: # quick memory · ! shell · @file attach ──
|
|
555
|
+
{
|
|
556
|
+
const macros = require("./input_macros");
|
|
557
|
+
if (macros.isHashMemory(inp)) {
|
|
558
|
+
try {
|
|
559
|
+
const { appendQuickMemory } = require("../core/skymd");
|
|
560
|
+
const file = appendQuickMemory(macros.hashNote(inp));
|
|
561
|
+
currentAgent.reloadProjectMemory();
|
|
562
|
+
process.stdout.write(chalk.green(" ✦ 已记入 ") + chalk.dim(file + "\n"));
|
|
563
|
+
} catch (e: any) { process.stdout.write(chalk.dim(` 记忆写入失败: ${e?.message || e}\n`)); }
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (macros.isBangCommand(inp)) {
|
|
567
|
+
const cmd = macros.bangCommand(inp);
|
|
568
|
+
process.stdout.write(chalk.dim(` $ ${cmd}\n`));
|
|
569
|
+
const r = macros.runBang(cmd);
|
|
570
|
+
process.stdout.write(" " + r.output.split("\n").slice(0, 40).join("\n ") + "\n");
|
|
571
|
+
currentAgent.memory.addMessage("system", `[用户执行 shell] $ ${cmd}\n${r.output.slice(0, 4000)}`);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
const expanded = macros.expandFileRefs(inp);
|
|
575
|
+
if (expanded.attached.length) process.stdout.write(chalk.dim(` 已附加 ${expanded.attached.map((f: string) => "@" + f).join(" ")}\n`));
|
|
576
|
+
inp = expanded.text;
|
|
577
|
+
}
|
|
578
|
+
|
|
427
579
|
// ── Chat (real streaming) ──
|
|
428
580
|
try {
|
|
429
581
|
await streamResponse(currentAgent, inp);
|
|
@@ -438,6 +590,79 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
|
438
590
|
process.exit(0);
|
|
439
591
|
}
|
|
440
592
|
|
|
593
|
+
/* ═══════════════════════════════════════
|
|
594
|
+
Headless mode — sky -p "..." (pipes, CI, external orchestrators)
|
|
595
|
+
═══════════════════════════════════════ */
|
|
596
|
+
async function readStdin(): Promise<string> {
|
|
597
|
+
if (process.stdin.isTTY) return "";
|
|
598
|
+
let data = "";
|
|
599
|
+
for await (const chunk of process.stdin) data += chunk;
|
|
600
|
+
return data.trim();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function runHeadless(
|
|
604
|
+
prompt: string,
|
|
605
|
+
opts: { agent?: string; json?: boolean; streamJson?: boolean }
|
|
606
|
+
): Promise<void> {
|
|
607
|
+
// Fresh session: a one-shot invocation must not inherit (or pollute) the
|
|
608
|
+
// interactive resume chain.
|
|
609
|
+
process.env.WA_NO_RESUME = "1";
|
|
610
|
+
const t0 = Date.now();
|
|
611
|
+
const ctx = createSystemContext();
|
|
612
|
+
const agentName = opts.agent || "fog";
|
|
613
|
+
const agent = ctx.agentMap.get(agentName);
|
|
614
|
+
if (!agent) { process.stderr.write(`Unknown agent: ${agentName}\n`); process.exit(1); }
|
|
615
|
+
await agent.init();
|
|
616
|
+
|
|
617
|
+
// No human to approve: dangerous tools are denied unless explicitly allowed.
|
|
618
|
+
try {
|
|
619
|
+
const { getSecurity } = require("../core/security");
|
|
620
|
+
getSecurity().setApprovalCallback(async () =>
|
|
621
|
+
process.env.SKYLOOM_ALLOW_DANGEROUS === "1");
|
|
622
|
+
} catch { /* optional */ }
|
|
623
|
+
|
|
624
|
+
const emit = (obj: Record<string, any>) => process.stdout.write(JSON.stringify(obj) + "\n");
|
|
625
|
+
let content = "";
|
|
626
|
+
let ok = true;
|
|
627
|
+
try {
|
|
628
|
+
for await (const ev of agent.chatStream(prompt)) {
|
|
629
|
+
if (opts.streamJson) { emit(ev); if (ev.type === "content") content += ev.text; continue; }
|
|
630
|
+
switch (ev.type) {
|
|
631
|
+
case "content":
|
|
632
|
+
content += ev.text;
|
|
633
|
+
if (!opts.json) process.stdout.write(String(ev.text));
|
|
634
|
+
break;
|
|
635
|
+
case "tool_status":
|
|
636
|
+
if (!opts.json) process.stderr.write(`[tool] ${ev.tool_name} ${ev.label || ""}\n`);
|
|
637
|
+
break;
|
|
638
|
+
case "truncated":
|
|
639
|
+
ok = false;
|
|
640
|
+
process.stderr.write(`[truncated] ${ev.reason}\n`);
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
} catch (e: any) {
|
|
645
|
+
ok = false;
|
|
646
|
+
process.stderr.write(`Error: ${e?.message || e}\n`);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (opts.json || opts.streamJson) {
|
|
650
|
+
emit({
|
|
651
|
+
type: "result",
|
|
652
|
+
success: ok,
|
|
653
|
+
agent: agentName,
|
|
654
|
+
model: (() => { try { return agent.contextUsage().model; } catch { return undefined; } })(),
|
|
655
|
+
content,
|
|
656
|
+
cost_usd: (() => { try { return ctx.llm.getTotalCost(); } catch { return 0; } })(),
|
|
657
|
+
duration_ms: Date.now() - t0,
|
|
658
|
+
});
|
|
659
|
+
} else if (content && !content.endsWith("\n")) {
|
|
660
|
+
process.stdout.write("\n");
|
|
661
|
+
}
|
|
662
|
+
await ctx.closeAll();
|
|
663
|
+
process.exit(ok ? 0 : 1);
|
|
664
|
+
}
|
|
665
|
+
|
|
441
666
|
/* ═══════════════════════════════════════
|
|
442
667
|
Task
|
|
443
668
|
═══════════════════════════════════════ */
|
|
@@ -456,13 +681,34 @@ async function runTask(goal: string): Promise<void> {
|
|
|
456
681
|
═══════════════════════════════════════ */
|
|
457
682
|
async function main() {
|
|
458
683
|
const args = process.argv.slice(2);
|
|
459
|
-
|
|
460
|
-
|
|
684
|
+
const classic = args.includes("--classic");
|
|
685
|
+
|
|
686
|
+
// ── headless: sky -p "prompt" [--agent fog] [--json | --stream-json] ──
|
|
687
|
+
const pIdx = args.findIndex((a) => a === "-p" || a === "--print");
|
|
688
|
+
if (pIdx >= 0) {
|
|
689
|
+
const flags = new Set(args);
|
|
690
|
+
const agentIdx = args.findIndex((a) => a === "--agent");
|
|
691
|
+
const agentName = agentIdx >= 0 ? args[agentIdx + 1] : undefined;
|
|
692
|
+
const inline = args[pIdx + 1] && !args[pIdx + 1].startsWith("-") ? args[pIdx + 1] : "";
|
|
693
|
+
const piped = await readStdin();
|
|
694
|
+
const prompt = [inline, piped].filter(Boolean).join("\n\n");
|
|
695
|
+
if (!prompt) { process.stderr.write('Usage: sky -p "prompt" [--agent fog] [--json|--stream-json]\n'); process.exit(1); }
|
|
696
|
+
await runHeadless(prompt, {
|
|
697
|
+
agent: agentName,
|
|
698
|
+
json: flags.has("--json"),
|
|
699
|
+
streamJson: flags.has("--stream-json"),
|
|
700
|
+
});
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const rest = args.filter((a) => a !== "--classic");
|
|
705
|
+
if (rest.length === 0) { await chat("fog", undefined, classic); return; }
|
|
706
|
+
if ((AGENT_NAMES as readonly string[]).includes(rest[0])) {
|
|
461
707
|
let m: string | undefined;
|
|
462
|
-
for (let i = 1; i <
|
|
463
|
-
await chat(
|
|
708
|
+
for (let i = 1; i < rest.length; i++) if ((rest[i] === "-m" || rest[i] === "--model") && i + 1 < rest.length) m = rest[++i];
|
|
709
|
+
await chat(rest[0], m, classic); return;
|
|
464
710
|
}
|
|
465
|
-
if (!["chat", "task", "web", "config", "init", "version", "mcp", "help"].includes(
|
|
711
|
+
if (!["chat", "task", "web", "config", "init", "version", "mcp", "help"].includes(rest[0]) && !rest[0].startsWith("-")) { await chat("fog", undefined, classic); return; }
|
|
466
712
|
program.parse(process.argv);
|
|
467
713
|
}
|
|
468
714
|
|
package/src/cli/mode.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive mode controller — single source of truth for plan/auto/default.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export enum InteractiveMode {
|
|
6
|
-
DEFAULT = 'default',
|
|
7
|
-
PLAN = 'plan',
|
|
8
|
-
AUTO = 'auto',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const CYCLE: InteractiveMode[] = [
|
|
12
|
-
InteractiveMode.DEFAULT,
|
|
13
|
-
InteractiveMode.PLAN,
|
|
14
|
-
InteractiveMode.AUTO,
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
const LABEL_STYLE: Record<InteractiveMode, [string, string]> = {
|
|
18
|
-
[InteractiveMode.DEFAULT]: ['DEFAULT', 'bold cyan'],
|
|
19
|
-
[InteractiveMode.PLAN]: ['PLAN', 'bold magenta'],
|
|
20
|
-
[InteractiveMode.AUTO]: ['AUTO', 'bold yellow'],
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const DESCRIPTIONS: Record<InteractiveMode, string> = {
|
|
24
|
-
[InteractiveMode.DEFAULT]: 'smart — router picks the right depth per message',
|
|
25
|
-
[InteractiveMode.PLAN]: 'plan first, confirm before execute',
|
|
26
|
-
[InteractiveMode.AUTO]: 'autonomous reasoning & execution',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export class ModeController {
|
|
30
|
-
private _mode: InteractiveMode | null = null;
|
|
31
|
-
|
|
32
|
-
get current(): InteractiveMode {
|
|
33
|
-
if (this._mode === null) {
|
|
34
|
-
this._mode = InteractiveMode.DEFAULT;
|
|
35
|
-
}
|
|
36
|
-
return this._mode;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
set(mode: InteractiveMode): InteractiveMode {
|
|
40
|
-
this._mode = mode;
|
|
41
|
-
return mode;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
cycle(): InteractiveMode {
|
|
45
|
-
const cur = this.current;
|
|
46
|
-
const idx = CYCLE.indexOf(cur);
|
|
47
|
-
const next = CYCLE[(idx + 1) % CYCLE.length];
|
|
48
|
-
return this.set(next);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
label(): [string, string] {
|
|
52
|
-
return LABEL_STYLE[this.current];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
describe(): string {
|
|
56
|
-
return DESCRIPTIONS[this.current];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Interactive mode controller — single source of truth for plan/auto/default.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export enum InteractiveMode {
|
|
6
|
+
DEFAULT = 'default',
|
|
7
|
+
PLAN = 'plan',
|
|
8
|
+
AUTO = 'auto',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const CYCLE: InteractiveMode[] = [
|
|
12
|
+
InteractiveMode.DEFAULT,
|
|
13
|
+
InteractiveMode.PLAN,
|
|
14
|
+
InteractiveMode.AUTO,
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const LABEL_STYLE: Record<InteractiveMode, [string, string]> = {
|
|
18
|
+
[InteractiveMode.DEFAULT]: ['DEFAULT', 'bold cyan'],
|
|
19
|
+
[InteractiveMode.PLAN]: ['PLAN', 'bold magenta'],
|
|
20
|
+
[InteractiveMode.AUTO]: ['AUTO', 'bold yellow'],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const DESCRIPTIONS: Record<InteractiveMode, string> = {
|
|
24
|
+
[InteractiveMode.DEFAULT]: 'smart — router picks the right depth per message',
|
|
25
|
+
[InteractiveMode.PLAN]: 'plan first, confirm before execute',
|
|
26
|
+
[InteractiveMode.AUTO]: 'autonomous reasoning & execution',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class ModeController {
|
|
30
|
+
private _mode: InteractiveMode | null = null;
|
|
31
|
+
|
|
32
|
+
get current(): InteractiveMode {
|
|
33
|
+
if (this._mode === null) {
|
|
34
|
+
this._mode = InteractiveMode.DEFAULT;
|
|
35
|
+
}
|
|
36
|
+
return this._mode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set(mode: InteractiveMode): InteractiveMode {
|
|
40
|
+
this._mode = mode;
|
|
41
|
+
return mode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
cycle(): InteractiveMode {
|
|
45
|
+
const cur = this.current;
|
|
46
|
+
const idx = CYCLE.indexOf(cur);
|
|
47
|
+
const next = CYCLE[(idx + 1) % CYCLE.length];
|
|
48
|
+
return this.set(next);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
label(): [string, string] {
|
|
52
|
+
return LABEL_STYLE[this.current];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe(): string {
|
|
56
|
+
return DESCRIPTIONS[this.current];
|
|
57
|
+
}
|
|
58
|
+
}
|