skyloom 1.13.5 → 1.13.7

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.
Files changed (195) hide show
  1. package/.github/workflows/ci.yml +36 -36
  2. package/README.md +220 -159
  3. package/config/providers.yaml +39 -39
  4. package/config/skills/api_integrator/SKILL.md +15 -15
  5. package/config/skills/arch_designer/SKILL.md +13 -13
  6. package/config/skills/ci_cd_manager/SKILL.md +14 -14
  7. package/config/skills/code_analysis/SKILL.md +13 -13
  8. package/config/skills/code_generator/SKILL.md +12 -12
  9. package/config/skills/code_reviewer/SKILL.md +13 -13
  10. package/config/skills/content_writer/SKILL.md +14 -14
  11. package/config/skills/data_transformer/SKILL.md +15 -15
  12. package/config/skills/document_analysis/SKILL.md +13 -13
  13. package/config/skills/emotional_companion/SKILL.md +15 -15
  14. package/config/skills/performance_checker/SKILL.md +14 -14
  15. package/config/skills/security_auditor/SKILL.md +14 -14
  16. package/config/skills/self_evolve/SKILL.md +13 -13
  17. package/config/skills/sys_operator/SKILL.md +15 -15
  18. package/config/skills/task_planner/SKILL.md +14 -14
  19. package/config/skills/web_research/SKILL.md +14 -14
  20. package/config/skills/workflow_designer/SKILL.md +13 -13
  21. package/dist/agents/dew.js +52 -52
  22. package/dist/agents/fair.js +84 -84
  23. package/dist/agents/fog.js +30 -30
  24. package/dist/agents/frost.js +32 -32
  25. package/dist/agents/rain.js +32 -32
  26. package/dist/agents/snow.js +68 -68
  27. package/dist/cli/commands_md.d.ts +41 -0
  28. package/dist/cli/commands_md.d.ts.map +1 -0
  29. package/dist/cli/commands_md.js +140 -0
  30. package/dist/cli/commands_md.js.map +1 -0
  31. package/dist/cli/input_macros.d.ts +28 -0
  32. package/dist/cli/input_macros.d.ts.map +1 -0
  33. package/dist/cli/input_macros.js +120 -0
  34. package/dist/cli/input_macros.js.map +1 -0
  35. package/dist/cli/loom.d.ts +220 -0
  36. package/dist/cli/loom.d.ts.map +1 -0
  37. package/dist/cli/loom.js +1094 -0
  38. package/dist/cli/loom.js.map +1 -0
  39. package/dist/cli/loom_chat.d.ts +20 -0
  40. package/dist/cli/loom_chat.d.ts.map +1 -0
  41. package/dist/cli/loom_chat.js +685 -0
  42. package/dist/cli/loom_chat.js.map +1 -0
  43. package/dist/cli/main.js +310 -14
  44. package/dist/cli/main.js.map +1 -1
  45. package/dist/cli/tui.d.ts.map +1 -1
  46. package/dist/cli/tui.js +7 -1
  47. package/dist/cli/tui.js.map +1 -1
  48. package/dist/core/agent/guard.d.ts +45 -0
  49. package/dist/core/agent/guard.d.ts.map +1 -0
  50. package/dist/core/agent/guard.js +113 -0
  51. package/dist/core/agent/guard.js.map +1 -0
  52. package/dist/core/agent.d.ts +17 -0
  53. package/dist/core/agent.d.ts.map +1 -1
  54. package/dist/core/agent.js +182 -93
  55. package/dist/core/agent.js.map +1 -1
  56. package/dist/core/factory.d.ts.map +1 -1
  57. package/dist/core/factory.js +34 -2
  58. package/dist/core/factory.js.map +1 -1
  59. package/dist/core/file_checkpoint.d.ts +57 -0
  60. package/dist/core/file_checkpoint.d.ts.map +1 -0
  61. package/dist/core/file_checkpoint.js +162 -0
  62. package/dist/core/file_checkpoint.js.map +1 -0
  63. package/dist/core/hooks.d.ts +43 -0
  64. package/dist/core/hooks.d.ts.map +1 -0
  65. package/dist/core/hooks.js +110 -0
  66. package/dist/core/hooks.js.map +1 -0
  67. package/dist/core/llm.d.ts.map +1 -1
  68. package/dist/core/llm.js +15 -9
  69. package/dist/core/llm.js.map +1 -1
  70. package/dist/core/longdoc.js +5 -5
  71. package/dist/core/mcp.d.ts +16 -0
  72. package/dist/core/mcp.d.ts.map +1 -1
  73. package/dist/core/mcp.js +55 -0
  74. package/dist/core/mcp.js.map +1 -1
  75. package/dist/core/model_config.d.ts +40 -0
  76. package/dist/core/model_config.d.ts.map +1 -0
  77. package/dist/core/model_config.js +191 -0
  78. package/dist/core/model_config.js.map +1 -0
  79. package/dist/core/skill.d.ts +7 -0
  80. package/dist/core/skill.d.ts.map +1 -1
  81. package/dist/core/skill.js +47 -0
  82. package/dist/core/skill.js.map +1 -1
  83. package/dist/core/skymd.d.ts +39 -0
  84. package/dist/core/skymd.d.ts.map +1 -0
  85. package/dist/core/skymd.js +177 -0
  86. package/dist/core/skymd.js.map +1 -0
  87. package/dist/core/tool.d.ts +12 -0
  88. package/dist/core/tool.d.ts.map +1 -1
  89. package/dist/core/tool.js +30 -0
  90. package/dist/core/tool.js.map +1 -1
  91. package/dist/core/verify.d.ts +27 -0
  92. package/dist/core/verify.d.ts.map +1 -0
  93. package/dist/core/verify.js +62 -0
  94. package/dist/core/verify.js.map +1 -0
  95. package/dist/skills/loader.d.ts +22 -2
  96. package/dist/skills/loader.d.ts.map +1 -1
  97. package/dist/skills/loader.js +45 -15
  98. package/dist/skills/loader.js.map +1 -1
  99. package/dist/tools/builtin.d.ts.map +1 -1
  100. package/dist/tools/builtin.js +13 -3
  101. package/dist/tools/builtin.js.map +1 -1
  102. package/dist/tools/model_tool.d.ts +11 -0
  103. package/dist/tools/model_tool.d.ts.map +1 -0
  104. package/dist/tools/model_tool.js +71 -0
  105. package/dist/tools/model_tool.js.map +1 -0
  106. package/dist/tools/todo.d.ts +30 -0
  107. package/dist/tools/todo.d.ts.map +1 -0
  108. package/dist/tools/todo.js +78 -0
  109. package/dist/tools/todo.js.map +1 -0
  110. package/docs/AESTHETIC_DESIGN.md +152 -144
  111. package/docs/OPTIMIZATION_PLAN.md +178 -178
  112. package/package.json +1 -1
  113. package/scripts/install.js +48 -48
  114. package/scripts/link.js +10 -10
  115. package/setup.bat +79 -79
  116. package/skill-test-ty2fOA/test.md +10 -10
  117. package/src/agents/dew.ts +70 -70
  118. package/src/agents/fair.ts +102 -102
  119. package/src/agents/fog.ts +48 -48
  120. package/src/agents/frost.ts +50 -50
  121. package/src/agents/rain.ts +50 -50
  122. package/src/agents/snow.ts +239 -239
  123. package/src/cli/commands_md.ts +112 -0
  124. package/src/cli/input_macros.ts +83 -0
  125. package/src/cli/loom.ts +982 -0
  126. package/src/cli/loom_chat.ts +598 -0
  127. package/src/cli/main.ts +255 -9
  128. package/src/cli/mode.ts +58 -58
  129. package/src/cli/tui.ts +228 -222
  130. package/src/core/agent/guard.ts +134 -0
  131. package/src/core/agent/task.ts +100 -100
  132. package/src/core/agent.ts +177 -95
  133. package/src/core/arbitrate.ts +162 -162
  134. package/src/core/catalog.ts +178 -178
  135. package/src/core/checkpoint.ts +94 -94
  136. package/src/core/estimate.ts +104 -104
  137. package/src/core/evolve.ts +191 -191
  138. package/src/core/factory.ts +31 -2
  139. package/src/core/file_checkpoint.ts +136 -0
  140. package/src/core/filter.ts +103 -103
  141. package/src/core/graph.ts +156 -156
  142. package/src/core/hooks.ts +126 -0
  143. package/src/core/icons.ts +53 -53
  144. package/src/core/index.ts +37 -37
  145. package/src/core/learn.ts +146 -146
  146. package/src/core/llm.ts +15 -9
  147. package/src/core/longdoc.ts +155 -155
  148. package/src/core/mcp.ts +48 -0
  149. package/src/core/mcp_server.ts +176 -176
  150. package/src/core/model_config.ts +157 -0
  151. package/src/core/profile.ts +255 -255
  152. package/src/core/router.ts +124 -124
  153. package/src/core/sandbox.ts +142 -142
  154. package/src/core/security.ts +243 -243
  155. package/src/core/skill.ts +42 -0
  156. package/src/core/skymd.ts +143 -0
  157. package/src/core/theme.ts +65 -65
  158. package/src/core/tool.ts +30 -0
  159. package/src/core/tool_router.ts +193 -193
  160. package/src/core/vector.ts +152 -152
  161. package/src/core/verify.ts +71 -0
  162. package/src/core/workspace.ts +150 -150
  163. package/src/plugins/loader.ts +66 -66
  164. package/src/skills/loader.ts +45 -16
  165. package/src/sql.js.d.ts +29 -29
  166. package/src/tools/builtin.ts +13 -3
  167. package/src/tools/computer.ts +269 -269
  168. package/src/tools/delegate.ts +49 -49
  169. package/src/tools/model_tool.ts +74 -0
  170. package/src/tools/todo.ts +76 -0
  171. package/src/web/tts.ts +93 -93
  172. package/tests/agent.test.ts +159 -159
  173. package/tests/agent_helpers.test.ts +48 -48
  174. package/tests/bus.test.ts +121 -121
  175. package/tests/catalog.test.ts +86 -86
  176. package/tests/checkpoint_commands.test.ts +124 -0
  177. package/tests/claude_compat.test.ts +110 -0
  178. package/tests/config.test.ts +41 -41
  179. package/tests/guard.test.ts +75 -0
  180. package/tests/icons.test.ts +45 -45
  181. package/tests/loom.test.ts +248 -0
  182. package/tests/memory.test.ts +170 -170
  183. package/tests/model_config.test.ts +109 -0
  184. package/tests/router.test.ts +86 -86
  185. package/tests/schemas.test.ts +51 -51
  186. package/tests/semantic.test.ts +83 -83
  187. package/tests/setup.ts +10 -10
  188. package/tests/skill.test.ts +172 -172
  189. package/tests/skymd.test.ts +146 -0
  190. package/tests/task.test.ts +60 -60
  191. package/tests/todo_toolstats.test.ts +94 -0
  192. package/tests/tool.test.ts +108 -108
  193. package/tests/tool_router.test.ts +71 -71
  194. package/tests/tui.test.ts +67 -67
  195. package/vitest.config.ts +17 -17
@@ -0,0 +1,685 @@
1
+ "use strict";
2
+ /**
3
+ * 立轴模式的对话主循环 — drives LoomUI with real agents.
4
+ *
5
+ * Streaming, slash commands, and the multi-agent "织谱" (weave chart): when
6
+ * /task orchestration runs, every sub-task is a live line in the viewport,
7
+ * the left rail badges each agent's tally, and shuttles fly across the sky
8
+ * band in their mineral pigments. All of it is event-driven off the
9
+ * orchestrator's onPlanned/onTaskStart/onTaskDone/onToolStatus callbacks.
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.loomChat = loomChat;
16
+ const chalk_1 = __importDefault(require("chalk"));
17
+ const theme_1 = require("../core/theme");
18
+ const factory_1 = require("../core/factory");
19
+ const skymd_1 = require("../core/skymd");
20
+ const verify_1 = require("../core/verify");
21
+ const mode_1 = require("./mode");
22
+ const input_macros_1 = require("./input_macros");
23
+ const commands_md_1 = require("./commands_md");
24
+ const file_checkpoint_1 = require("../core/file_checkpoint");
25
+ const loom_1 = require("./loom");
26
+ const OK_HEX = "#3a7a6e"; // 石绿 — success
27
+ const ERR_HEX = "#b3342d"; // 朱砂 — failure
28
+ const AGENT_NAMES = ["fog", "rain", "frost", "snow", "dew", "fair"];
29
+ function fmtCost(c) {
30
+ if (c >= 1)
31
+ return `$${c.toFixed(2)}`;
32
+ if (c >= 0.01)
33
+ return `$${c.toFixed(4)}`;
34
+ if (c > 0)
35
+ return `${(c * 100).toFixed(2)}¢`;
36
+ return "$0";
37
+ }
38
+ function fmtMs(ms) {
39
+ if (ms === undefined)
40
+ return "";
41
+ return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
42
+ }
43
+ /* ════════════════════════════════════════
44
+ Streaming a single turn into the loom
45
+ ════════════════════════════════════════ */
46
+ let turnSeq = 0;
47
+ async function loomStream(ui, agent, input) {
48
+ const t = (0, theme_1.agentTheme)(agent.name);
49
+ const turn = ++turnSeq;
50
+ ui.busy = true;
51
+ ui.busyLabel = "思忖";
52
+ const controller = new AbortController();
53
+ let interrupted = false;
54
+ ui.onInterrupt = () => {
55
+ if (!interrupted) {
56
+ interrupted = true;
57
+ controller.abort();
58
+ ui.flash("⊘ 中断本轮(保留已写内容)");
59
+ }
60
+ };
61
+ let headerShown = false;
62
+ let streaming = false;
63
+ let reasonText = "";
64
+ let toolSeq = 0;
65
+ let lastToolId = "";
66
+ try {
67
+ for await (const ev of agent.chatStream(input, controller.signal)) {
68
+ switch (ev.type) {
69
+ case "interrupted":
70
+ interrupted = true;
71
+ break;
72
+ case "reasoning": {
73
+ ui.busyLabel = "思考";
74
+ reasonText = (reasonText + String(ev.text).replace(/\s+/g, " ")).slice(-72);
75
+ ui.line(chalk_1.default.dim("◦ 思考 ") + chalk_1.default.dim.italic(reasonText), `reason-${turn}`);
76
+ break;
77
+ }
78
+ case "content":
79
+ ui.busyLabel = "书写";
80
+ if (!streaming) {
81
+ if (!headerShown) {
82
+ ui.beginStream(agent.name);
83
+ headerShown = true;
84
+ }
85
+ else
86
+ ui.continueStream();
87
+ streaming = true;
88
+ }
89
+ ui.streamWrite(String(ev.text));
90
+ break;
91
+ case "tool_status": {
92
+ ui.endStream();
93
+ streaming = false;
94
+ ui.busyLabel = String(ev.tool_name);
95
+ lastToolId = `tool-${turn}-${++toolSeq}`;
96
+ ui.line(chalk_1.default.hex(t.hex)(`${t.symbol} ${ev.tool_name}`) +
97
+ (ev.label ? chalk_1.default.dim(` ${ev.label}`) : "") + chalk_1.default.dim(" …"), lastToolId);
98
+ break;
99
+ }
100
+ case "tool_done":
101
+ if (lastToolId) {
102
+ ui.update(lastToolId, (ev.success ? chalk_1.default.hex(OK_HEX)("✓") : chalk_1.default.hex(ERR_HEX)("✗")) +
103
+ " " + chalk_1.default.dim(String(ev.tool_name)));
104
+ }
105
+ // live task checklist: re-render in place whenever the agent updates it
106
+ if (ev.tool_name === "todo_write" && ev.success) {
107
+ try {
108
+ const items = agent.memory.getWorking("todos") || [];
109
+ if (items.length) {
110
+ const { renderTodoList } = require("../tools/todo");
111
+ ui.text(renderTodoList(items), undefined, " ☰ ", "todo-live");
112
+ }
113
+ }
114
+ catch { /* checklist rendering is best-effort */ }
115
+ }
116
+ break;
117
+ case "truncated":
118
+ ui.endStream();
119
+ streaming = false;
120
+ ui.line(chalk_1.default.yellow(`⚠ ${ev.reason}`));
121
+ break;
122
+ case "done":
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ catch (e) {
128
+ if (!interrupted && e?.name !== "AbortError") {
129
+ ui.endStream();
130
+ ui.line(chalk_1.default.hex(ERR_HEX)("✗ ") + chalk_1.default.dim(String(e?.message || e)));
131
+ }
132
+ }
133
+ finally {
134
+ ui.endStream();
135
+ if (interrupted)
136
+ ui.line(chalk_1.default.dim("⊘ 已中断(保留以上内容)"));
137
+ ui.blank();
138
+ ui.busy = false;
139
+ ui.busyLabel = "";
140
+ ui.onInterrupt = null;
141
+ ui.turns++;
142
+ ui.paint();
143
+ }
144
+ }
145
+ /* ════════════════════════════════════════
146
+ Multi-agent orchestration — the live 织谱
147
+ ════════════════════════════════════════ */
148
+ function renderTaskLine(ui, t, spin, idxOf) {
149
+ const th = (0, theme_1.agentTheme)(t.agent);
150
+ const pigment = chalk_1.default.hex(th.hex);
151
+ let glyph;
152
+ switch (t.state) {
153
+ case "wait":
154
+ glyph = chalk_1.default.hex(theme_1.PALETTE.inkFaint)("·");
155
+ break;
156
+ case "run":
157
+ glyph = spin % 2 ? pigment(th.symbol) : pigment.dim(th.symbol);
158
+ break;
159
+ case "ok":
160
+ glyph = chalk_1.default.hex(OK_HEX)("✓");
161
+ break;
162
+ case "fail":
163
+ glyph = chalk_1.default.hex(ERR_HEX)("✗");
164
+ break;
165
+ }
166
+ const deps = t.deps.length
167
+ ? chalk_1.default.dim(" ←" + t.deps.map((d) => (0, loom_1.circled)(idxOf.get(d) ?? 0)).join(""))
168
+ : "";
169
+ const time = t.ms !== undefined ? chalk_1.default.dim(` (${fmtMs(t.ms)})`) : "";
170
+ const desc = t.state === "wait" ? chalk_1.default.dim(t.desc) : t.desc;
171
+ return ` ${glyph} ${chalk_1.default.dim((0, loom_1.circled)(t.index))} ${pigment(th.kanji)} ${(0, loom_1.cutVisual)(desc, 999)}${deps}${time}`;
172
+ }
173
+ async function runLoomTask(ui, ctx, goal) {
174
+ const t = (0, theme_1.agentTheme)(ui.agentName);
175
+ ui.busy = true;
176
+ ui.busyLabel = "织谱推演";
177
+ ui.onInterrupt = () => ui.flash("织造中 · 单梭无法中断 · 再按 Ctrl-C 强制退出");
178
+ ui.blank();
179
+ ui.line(chalk_1.default.bold.hex(t.hex)("✦ 織 ") + chalk_1.default.bold((0, loom_1.cutVisual)(goal, 999)));
180
+ ui.blank();
181
+ const idxOf = new Map();
182
+ let spin = 0;
183
+ const redraw = () => {
184
+ for (const id of ui.orch.order) {
185
+ const task = ui.orch.tasks.get(id);
186
+ ui.update(`task-${id}`, renderTaskLine(ui, task, spin, idxOf));
187
+ }
188
+ };
189
+ const spinner = setInterval(() => { spin++; redraw(); }, 480);
190
+ try {
191
+ const [, results, summary] = await (0, factory_1.orchestrateTask)(goal, ctx.agentMap, null, {
192
+ onPlanned: async (tasks) => {
193
+ ui.orch.plan(tasks);
194
+ for (const id of ui.orch.order)
195
+ idxOf.set(id, ui.orch.tasks.get(id).index);
196
+ ui.update("orch-head", "");
197
+ ui.line(chalk_1.default.hex(t.hex)("✦ 织谱") + chalk_1.default.dim(` · ${ui.orch.order.length} 梭`), "orch-head");
198
+ for (const id of ui.orch.order) {
199
+ const task = ui.orch.tasks.get(id);
200
+ ui.line(renderTaskLine(ui, task, 0, idxOf), `task-${id}`);
201
+ }
202
+ ui.line(chalk_1.default.dim(" ⸙ …"), "orch-tool");
203
+ ui.paint();
204
+ return true;
205
+ },
206
+ onTaskStart: async (task) => {
207
+ ui.orch.start(task.id);
208
+ ui.busyLabel = `织造 ${(0, theme_1.agentTheme)(task.assignedTo || "fog").kanji}`;
209
+ redraw();
210
+ ui.paint();
211
+ },
212
+ onTaskDone: async (task, r) => {
213
+ ui.orch.done(task.id, !!r.success);
214
+ const p = ui.orch.progress();
215
+ ui.busyLabel = `织造 ${p.done}/${p.total}`;
216
+ redraw();
217
+ ui.paint();
218
+ },
219
+ onToolStatus: (status) => {
220
+ ui.update("orch-tool", chalk_1.default.dim(` ⸙ ${(0, loom_1.cutVisual)(status, 64)}`));
221
+ },
222
+ });
223
+ ui.update("orch-tool", chalk_1.default.dim(" ⸙ 收梭"));
224
+ ui.blank();
225
+ if (results.length) {
226
+ ui.line(chalk_1.default.hex(t.hex)("─ 汇总 ") + chalk_1.default.dim("─".repeat(8)));
227
+ ui.blank();
228
+ ui.text(String(summary || ""));
229
+ }
230
+ else {
231
+ ui.line(chalk_1.default.dim(String(summary || "没有需要执行的任务。")));
232
+ }
233
+ ui.blank();
234
+ }
235
+ catch (e) {
236
+ ui.line(chalk_1.default.hex(ERR_HEX)("✗ 织造失败 ") + chalk_1.default.dim(String(e?.message || e)));
237
+ ui.blank();
238
+ }
239
+ finally {
240
+ clearInterval(spinner);
241
+ ui.orch.finish();
242
+ ui.busy = false;
243
+ ui.busyLabel = "";
244
+ ui.onInterrupt = null;
245
+ ui.turns++;
246
+ ui.paint();
247
+ }
248
+ }
249
+ /* ════════════════════════════════════════
250
+ Welcome scroll
251
+ ════════════════════════════════════════ */
252
+ function welcome(ui, version) {
253
+ const t = (0, theme_1.agentTheme)(ui.agentName);
254
+ ui.blank();
255
+ ui.line(chalk_1.default.bold.hex(t.hex)(" ✦ 天空织机 ") + chalk_1.default.dim(`v${version}`));
256
+ ui.blank();
257
+ ui.line(" " + chalk_1.default.hex(t.hex).italic(t.poem));
258
+ ui.blank();
259
+ ui.line(chalk_1.default.dim(" / 命令 · /task 多灵织造 · Shift+Tab 切模式 · @文件 !命令 #记忆"));
260
+ ui.blank();
261
+ }
262
+ async function loomChat(ctx, startAgent, deps) {
263
+ let agent = startAgent;
264
+ let lastSessions = [];
265
+ const ui = new loom_1.LoomUI();
266
+ ui.agentName = agent.name;
267
+ ui.statusRight = () => {
268
+ try {
269
+ const cu = agent.contextUsage();
270
+ const pct = cu.pct || 0;
271
+ const t = (0, theme_1.agentTheme)(agent.name);
272
+ const cells = 5;
273
+ const filled = Math.round((pct / 100) * cells);
274
+ const bar = chalk_1.default.hex(t.hex)("▰".repeat(filled)) + chalk_1.default.hex(theme_1.PALETTE.inkFaint)("▱".repeat(cells - filled));
275
+ return chalk_1.default.dim(`${cu.model || "?"} · ${fmtCost(ctx.llm.getTotalCost())} · `) + bar + chalk_1.default.dim(` ${pct}%`);
276
+ }
277
+ catch {
278
+ return "";
279
+ }
280
+ };
281
+ // ── Interactive modes (Shift+Tab cycles default → plan → auto) ──
282
+ const mode = new mode_1.ModeController();
283
+ const applyMode = () => {
284
+ const m = mode.current;
285
+ agent.planMode = m === mode_1.InteractiveMode.PLAN;
286
+ ui.modeBadge =
287
+ m === mode_1.InteractiveMode.PLAN ? chalk_1.default.hex("#8a8a82").bold("◇ 计划 · 只读出方案") :
288
+ m === mode_1.InteractiveMode.AUTO ? chalk_1.default.hex("#b3342d").bold("⚡ 自动 · 免审批") : "";
289
+ if (m !== mode_1.InteractiveMode.DEFAULT) {
290
+ ui.flash(m === mode_1.InteractiveMode.PLAN ? "计划模式:只读调研 → 出方案 → 批准后切回执行" : "自动模式:危险工具免审批,注意风险");
291
+ }
292
+ };
293
+ ui.onModeCycle = () => { mode.cycle(); applyMode(); };
294
+ // Tool approval becomes a loom-native modal instead of a raw readline prompt.
295
+ // AUTO mode approves automatically (with a visible trace line).
296
+ try {
297
+ const { getSecurity } = require("../core/security");
298
+ getSecurity().setApprovalCallback(async (tool, args, level) => {
299
+ if (mode.current === mode_1.InteractiveMode.AUTO) {
300
+ ui.line(chalk_1.default.dim(` ⚡ 自动批准 ${tool} (危险等级 ${level})`));
301
+ return true;
302
+ }
303
+ const summary = `${tool} (危险等级 ${level}) ${JSON.stringify(args).slice(0, 48)}`;
304
+ return ui.confirm(summary);
305
+ });
306
+ }
307
+ catch { /* security module optional */ }
308
+ ui.start();
309
+ welcome(ui, deps.version);
310
+ const say = (s) => { ui.line(s); };
311
+ const dim = (s) => { ui.line(chalk_1.default.dim(" " + s)); };
312
+ // 自定义斜杠命令(.sky/commands/ + ~/.skyloom/commands/),每轮重扫即时生效
313
+ let customCommands = (0, commands_md_1.loadCustomCommands)();
314
+ ui.extraCommands = customCommands.map((c) => ["/" + c.name, c.description]);
315
+ try {
316
+ while (true) {
317
+ const inp = await ui.readInput();
318
+ if (!inp)
319
+ continue;
320
+ const cmdL = inp.toLowerCase();
321
+ if (inp.startsWith("/")) {
322
+ customCommands = (0, commands_md_1.loadCustomCommands)();
323
+ ui.extraCommands = customCommands.map((c) => ["/" + c.name, c.description]);
324
+ }
325
+ if (cmdL === "/quit" || cmdL === "/exit")
326
+ break;
327
+ // agent switch — stamp a seal
328
+ let switched = false;
329
+ for (const n of AGENT_NAMES) {
330
+ if (cmdL === "/" + n) {
331
+ const a = ctx.agentMap.get(n);
332
+ if (a) {
333
+ await a.init();
334
+ agent.planMode = false;
335
+ agent = a;
336
+ applyMode(); // plan mode follows the session, not the agent instance
337
+ ui.agentName = n;
338
+ const t = (0, theme_1.agentTheme)(n);
339
+ ui.blank();
340
+ say(" " + chalk_1.default.bgHex(t.hex).hex(theme_1.PALETTE.paper).bold(` ${t.kanji} `) + " " + chalk_1.default.bold.hex(t.hex)(t.pigment) + chalk_1.default.dim(` · ${t.specialty}`));
341
+ say(" " + chalk_1.default.hex(t.hex).italic(t.poem));
342
+ ui.blank();
343
+ }
344
+ switched = true;
345
+ break;
346
+ }
347
+ }
348
+ if (switched)
349
+ continue;
350
+ if (cmdL === "/clear") {
351
+ ui.clearViewport();
352
+ continue;
353
+ }
354
+ if (cmdL === "/" || cmdL === "/help") {
355
+ dim("输入 / 后键入字母筛选命令,Tab 补全,↑↓ 选择,Shift+Tab 切模式。");
356
+ continue;
357
+ }
358
+ if (cmdL === "/plan" || cmdL === "/auto" || cmdL === "/default") {
359
+ mode.set(cmdL === "/plan" ? mode_1.InteractiveMode.PLAN : cmdL === "/auto" ? mode_1.InteractiveMode.AUTO : mode_1.InteractiveMode.DEFAULT);
360
+ applyMode();
361
+ dim(`模式 → ${mode.current}${mode.current === "default" ? "" : " · Shift+Tab 或 /default 切回"}`);
362
+ continue;
363
+ }
364
+ if (cmdL === "/context") {
365
+ try {
366
+ const d = agent.contextDetail();
367
+ ui.blank();
368
+ say(" " + chalk_1.default.bold(`上下文 ${d.estimatedTokens}/${d.maxTokens} tokens (${d.pct}%)`) + chalk_1.default.dim(` · ${d.model}`));
369
+ dim(`系统提示 ≈${d.systemPromptTokens} tokens · 工具 ${d.toolCount} 个 · 技能 ${d.activeSkills.length ? d.activeSkills.join(",") : "无"}`);
370
+ for (const [role, v] of Object.entries(d.byRole)) {
371
+ const bar = "▰".repeat(Math.max(1, Math.min(20, Math.round((v.tokens / Math.max(1, d.estimatedTokens)) * 20))));
372
+ dim(`${role.padEnd(9)} ${String(v.tokens).padStart(6)} tk · ${v.count} 条 ${bar}`);
373
+ }
374
+ ui.blank();
375
+ }
376
+ catch (e) {
377
+ dim(`无法获取: ${e?.message || e}`);
378
+ }
379
+ continue;
380
+ }
381
+ if (cmdL === "/tools") {
382
+ const stats = agent.toolRegistry?.getStats?.() || [];
383
+ if (!stats.length) {
384
+ dim("本会话当前灵还没有工具调用");
385
+ continue;
386
+ }
387
+ const t = (0, theme_1.agentTheme)(agent.name);
388
+ ui.blank();
389
+ say(" " + chalk_1.default.bold.hex(t.hex)(`${t.symbol} 工具调用`) + chalk_1.default.dim(` · ${agent.name}`));
390
+ for (const s of stats.slice(0, 12)) {
391
+ const fail = s.failures ? chalk_1.default.hex(ERR_HEX)(` ✗${s.failures}`) : "";
392
+ const cache = s.cacheHits ? chalk_1.default.dim(` ⊙${s.cacheHits}`) : "";
393
+ const breaker = s.breaker !== "closed" ? chalk_1.default.yellow(` [熔断:${s.breaker}]`) : "";
394
+ ui.line(` ${chalk_1.default.dim("·")} ${s.name.padEnd(16)} ${chalk_1.default.dim(`${s.calls} 次 · ${s.avgMs}ms`)}${fail}${cache}${breaker}`);
395
+ }
396
+ ui.blank();
397
+ continue;
398
+ }
399
+ if (cmdL === "/verify") {
400
+ const vc = (0, verify_1.resolveVerifyConfig)(ctx.config);
401
+ if (!vc.commands.length) {
402
+ dim("未配置验证命令 — 在 config.yaml 的 verify.commands 或 SKY.md 的 ## Verify 小节声明");
403
+ continue;
404
+ }
405
+ ui.busy = true;
406
+ ui.busyLabel = "验证";
407
+ ui.blank();
408
+ say(" " + chalk_1.default.bold("⚙ verify") + chalk_1.default.dim(` · ${vc.commands.length} 条命令`));
409
+ const vr = (0, verify_1.runVerify)(vc);
410
+ ui.busy = false;
411
+ ui.busyLabel = "";
412
+ for (const ln of vr.report.split("\n").slice(0, 30))
413
+ ui.line(" " + (ln.startsWith("✓") ? chalk_1.default.hex(OK_HEX)(ln) : ln.startsWith("✗") ? chalk_1.default.hex(ERR_HEX)(ln) : chalk_1.default.dim((0, loom_1.cutVisual)(ln, 200))));
414
+ if (!vr.ok)
415
+ dim(`验证失败 — 直接说「修复 verify 失败」让 ${agent.name} 处理`);
416
+ ui.blank();
417
+ continue;
418
+ }
419
+ if (cmdL === "/init") {
420
+ dim("开始扫描项目,生成 SKY.md 项目记忆 …");
421
+ ui.blank();
422
+ ui.text(skymd_1.INIT_PROMPT.split("\n")[0], (s) => chalk_1.default.hex(theme_1.PALETTE.inkLight)(s), chalk_1.default.hex(theme_1.PALETTE.inkLight)("❯ "));
423
+ await loomStream(ui, agent, skymd_1.INIT_PROMPT);
424
+ agent.reloadProjectMemory();
425
+ continue;
426
+ }
427
+ if (cmdL === "/version") {
428
+ dim(`Skyloom v${deps.version}`);
429
+ continue;
430
+ }
431
+ if (cmdL === "/status") {
432
+ dim(`${agent.displayName} (${agent.name}) · ${agent.state} · 记忆 ${agent.memory.shortTerm.length} 条`);
433
+ continue;
434
+ }
435
+ if (cmdL === "/cost") {
436
+ dim(`总费用 ${fmtCost(ctx.llm.getTotalCost())}`);
437
+ continue;
438
+ }
439
+ if (cmdL === "/cost reset") {
440
+ ctx.llm.resetUsageStats?.();
441
+ dim("已重置费用统计");
442
+ continue;
443
+ }
444
+ if (cmdL === "/compact") {
445
+ ui.busy = true;
446
+ ui.busyLabel = "压缩上下文";
447
+ try {
448
+ const r = await agent.compact();
449
+ say(" " + chalk_1.default.hex(OK_HEX)("✓ ") + chalk_1.default.dim(String(r)));
450
+ }
451
+ catch (e) {
452
+ say(" " + chalk_1.default.hex(ERR_HEX)("✗ ") + chalk_1.default.dim(String(e?.message || e)));
453
+ }
454
+ ui.busy = false;
455
+ ui.busyLabel = "";
456
+ continue;
457
+ }
458
+ if (cmdL === "/memory") {
459
+ dim(`短期 ${agent.memory.shortTerm.length} 条 · 工作记忆 ${Object.keys(agent.memory.working).length} 键`);
460
+ continue;
461
+ }
462
+ if (cmdL === "/memory clear") {
463
+ await agent.memory.clearShortTerm();
464
+ dim("记忆已清空");
465
+ continue;
466
+ }
467
+ if (cmdL === "/workspace") {
468
+ dim(String(ctx.workspacePath || "default"));
469
+ continue;
470
+ }
471
+ if (cmdL === "/mcp") {
472
+ dim(String(ctx.mcpStatus?.join(", ") || "none"));
473
+ continue;
474
+ }
475
+ if (cmdL === "/model" || cmdL.startsWith("/model ")) {
476
+ const { setAgentModel, setUnifiedModel, clearAgentModel, setAgentApiKey, describeAgentLLM } = require("../core/model_config");
477
+ const cfg = ctx.config;
478
+ const parts = inp.split(/\s+/).slice(1);
479
+ const t = (0, theme_1.agentTheme)(agent.name);
480
+ if (parts.length === 0) {
481
+ const d = describeAgentLLM(cfg, agent.name);
482
+ const keyLabel = { agent: "独立 key", env: "环境变量", global: "全局 key", missing: chalk_1.default.yellow("缺失!") }[d.keySource] || d.keySource;
483
+ ui.blank();
484
+ say(" " + chalk_1.default.bold.hex(t.hex)(`${t.symbol} ${agent.name}`) + chalk_1.default.bold(` · ${d.model}`) + chalk_1.default.dim(` (${d.source === "agent" ? "独立配置" : "统一配置"} · ${d.provider || "?"} · ${keyLabel})`));
485
+ dim(`统一默认: ${cfg.default_model || cfg.llm?.default_model || "gpt-4o"}`);
486
+ dim("/model <id> 给当前灵单独换 · /model unified <id> 改统一默认 · /model reset 回到统一 · /model key <key> 独立 key");
487
+ ui.blank();
488
+ continue;
489
+ }
490
+ if (parts[0] === "reset") {
491
+ clearAgentModel(cfg, agent.name);
492
+ say(" " + chalk_1.default.hex(OK_HEX)(`✓ ${agent.name} 已回到统一配置`) + chalk_1.default.dim(` · ${describeAgentLLM(cfg, agent.name).model}`));
493
+ continue;
494
+ }
495
+ if (parts[0] === "unified" || parts[0] === "default") {
496
+ if (!parts[1]) {
497
+ dim("用法: /model unified <模型id>");
498
+ continue;
499
+ }
500
+ const r = setUnifiedModel(cfg, parts[1]);
501
+ if (!r.ok) {
502
+ dim(`'${parts[1]}' 不在目录中${r.suggestions.length ? " · 可选: " + r.suggestions.join(", ") : ""}`);
503
+ continue;
504
+ }
505
+ say(" " + chalk_1.default.hex(OK_HEX)(`✓ 统一默认 → ${parts[1]}`) + chalk_1.default.dim(r.provider ? ` (${r.provider})` : ""));
506
+ continue;
507
+ }
508
+ if (parts[0] === "key") {
509
+ if (!parts[1]) {
510
+ dim("用法: /model key <api-key> — 仅当前灵使用");
511
+ continue;
512
+ }
513
+ setAgentApiKey(cfg, agent.name, parts[1]);
514
+ say(" " + chalk_1.default.hex(OK_HEX)(`✓ ${agent.name} 的独立 API key 已保存`));
515
+ continue;
516
+ }
517
+ const r = setAgentModel(cfg, agent.name, parts[0]);
518
+ if (!r.ok) {
519
+ dim(`'${parts[0]}' 不在目录中${r.suggestions.length ? " · 可选: " + r.suggestions.join(", ") : " · /setup 查看全部"}`);
520
+ continue;
521
+ }
522
+ say(" " + chalk_1.default.hex(OK_HEX)(`✓ ${agent.name} → ${parts[0]}`) + chalk_1.default.dim(`${r.provider ? ` (${r.provider})` : ""} · 下一条消息生效 · /model reset 撤销`));
523
+ const d = describeAgentLLM(cfg, agent.name);
524
+ if (d.keySource === "missing")
525
+ dim(`⚠ ${r.provider} 还没有 API key — /apikey set ${r.provider} <key> 或 /model key <key>`);
526
+ continue;
527
+ }
528
+ if (cmdL === "/sessions") {
529
+ lastSessions = await agent.memory.listSessions();
530
+ const active = agent.memory.getActiveSession();
531
+ const t = (0, theme_1.agentTheme)(agent.name);
532
+ ui.blank();
533
+ say(" " + chalk_1.default.bold.hex(t.hex)(`${t.symbol} ${t.kanji} 会话`) + chalk_1.default.dim(` (${lastSessions.length})`));
534
+ if (!lastSessions.length)
535
+ dim("(暂无历史会话)");
536
+ lastSessions.slice(0, 15).forEach((s, i) => {
537
+ const mark = s.id === active ? chalk_1.default.hex(t.hex)("●") : chalk_1.default.dim("·");
538
+ const preview = (s.preview || "(空)").replace(/\s+/g, " ").slice(0, 36);
539
+ say(` ${mark} ${chalk_1.default.dim(String(i + 1).padStart(2))} ${preview} ${chalk_1.default.dim(`· ${s.messageCount}条 · ${String(s.id).slice(0, 8)}`)}`);
540
+ });
541
+ dim("/resume <序号或id> 恢复 · /new 新会话");
542
+ ui.blank();
543
+ continue;
544
+ }
545
+ if (cmdL === "/new") {
546
+ await agent.memory.clearShortTerm();
547
+ const id = await agent.memory.createSession();
548
+ say(" " + chalk_1.default.hex(OK_HEX)("✦ 新会话已开始") + chalk_1.default.dim(` · ${String(id).slice(0, 8)}`));
549
+ continue;
550
+ }
551
+ if (cmdL === "/resume" || cmdL.startsWith("/resume ")) {
552
+ const arg = inp.slice(7).trim();
553
+ if (!lastSessions.length)
554
+ lastSessions = await agent.memory.listSessions();
555
+ let target = null;
556
+ if (!arg)
557
+ target = lastSessions[0];
558
+ else if (/^\d+$/.test(arg))
559
+ target = lastSessions[parseInt(arg) - 1];
560
+ else
561
+ target = lastSessions.find((s) => String(s.id).startsWith(arg));
562
+ if (!target) {
563
+ dim("未找到该会话。先 /sessions 看列表。");
564
+ continue;
565
+ }
566
+ const ok = await agent.memory.loadSession(target.id);
567
+ if (!ok) {
568
+ dim("恢复失败。");
569
+ continue;
570
+ }
571
+ const n = agent.memory.shortTerm.filter((m) => m.role !== "system").length;
572
+ const t = (0, theme_1.agentTheme)(agent.name);
573
+ say(" " + chalk_1.default.bold.hex(t.hex)("↺ 已恢复会话") + chalk_1.default.dim(` · ${n} 条消息 · ${String(target.id).slice(0, 8)}`));
574
+ continue;
575
+ }
576
+ if (cmdL.startsWith("/apikey set ")) {
577
+ const p = inp.split(/\s+/);
578
+ if (p.length >= 4) {
579
+ deps.saveApiKey(p[2], p[3]);
580
+ say(" " + chalk_1.default.hex(OK_HEX)(`✓ 已保存 ${p[2]} API key`));
581
+ }
582
+ else
583
+ dim("用法: /apikey set <provider> <key>");
584
+ continue;
585
+ }
586
+ if (cmdL === "/setup") {
587
+ const r = await ui.suspend(() => deps.setupWizard());
588
+ if (r)
589
+ say(" " + chalk_1.default.hex(OK_HEX)(`✓ ${r.provider} · ${r.model} · 就绪`));
590
+ continue;
591
+ }
592
+ if (cmdL.startsWith("/task ")) {
593
+ const goal = inp.slice(6).trim();
594
+ if (!goal) {
595
+ dim("用法: /task <目标>");
596
+ continue;
597
+ }
598
+ await runLoomTask(ui, ctx, goal);
599
+ continue;
600
+ }
601
+ if (cmdL === "/rewind" || cmdL.startsWith("/rewind ")) {
602
+ const cp = (0, file_checkpoint_1.getFileCheckpoints)();
603
+ const arg = inp.slice(7).trim();
604
+ const n = /^\d+$/.test(arg) ? parseInt(arg, 10) : 1;
605
+ const r = cp.rewind(n);
606
+ if (r.turns === 0) {
607
+ const turns = cp.list();
608
+ if (!turns.length) {
609
+ dim("没有可回退的文件改动(检查点覆盖 write/edit/delete_file;run_bash 的副作用无法回退)");
610
+ continue;
611
+ }
612
+ say(" " + chalk_1.default.bold("检查点") + chalk_1.default.dim(` · ${turns.length} 轮可回退 — /rewind [n]`));
613
+ for (const t of turns.slice(0, 8))
614
+ dim(`${t.label} · ${t.files.length} 个文件`);
615
+ continue;
616
+ }
617
+ say(" " + chalk_1.default.hex(OK_HEX)(`↺ 已回退 ${r.turns} 轮`) + chalk_1.default.dim(` · 恢复 ${r.restored.length} 个文件${r.deleted.length ? ` · 删除 ${r.deleted.length} 个新建文件` : ""}`));
618
+ for (const f of [...r.restored, ...r.deleted].slice(0, 10))
619
+ dim(f);
620
+ continue;
621
+ }
622
+ // ── 自定义斜杠命令 ──
623
+ if (inp.startsWith("/")) {
624
+ const hit = (0, commands_md_1.resolveCustomCommand)(inp, customCommands);
625
+ if (hit) {
626
+ if (hit.command.agent && ctx.agentMap.has(hit.command.agent)) {
627
+ const a = ctx.agentMap.get(hit.command.agent);
628
+ await a.init();
629
+ agent.planMode = false;
630
+ agent = a;
631
+ applyMode();
632
+ ui.agentName = a.name;
633
+ }
634
+ dim(`⌘ /${hit.command.name}` + (hit.command.agent ? ` → ${hit.command.agent}` : ""));
635
+ const expanded = (0, input_macros_1.expandFileRefs)(hit.prompt);
636
+ ui.blank();
637
+ ui.text((0, loom_1.cutVisual)(hit.prompt, 400), (s) => chalk_1.default.hex(theme_1.PALETTE.inkLight)(s), chalk_1.default.hex(theme_1.PALETTE.inkLight)("❯ "));
638
+ await loomStream(ui, agent, expanded.text);
639
+ continue;
640
+ }
641
+ }
642
+ if (inp.startsWith("/")) {
643
+ dim(`未知命令 ${inp.split(" ")[0]} · 输入 / 看全部命令`);
644
+ continue;
645
+ }
646
+ // ── input macros: # quick memory · ! shell · @file attach ──
647
+ if ((0, input_macros_1.isHashMemory)(inp)) {
648
+ try {
649
+ const file = (0, skymd_1.appendQuickMemory)((0, input_macros_1.hashNote)(inp));
650
+ agent.reloadProjectMemory();
651
+ say(" " + chalk_1.default.hex(OK_HEX)("✦ 已记入 ") + chalk_1.default.dim(file));
652
+ }
653
+ catch (e) {
654
+ dim(`记忆写入失败: ${e?.message || e}`);
655
+ }
656
+ continue;
657
+ }
658
+ if ((0, input_macros_1.isBangCommand)(inp)) {
659
+ const cmd = (0, input_macros_1.bangCommand)(inp);
660
+ ui.blank();
661
+ say(" " + chalk_1.default.hex(theme_1.PALETTE.inkLight)(`$ ${cmd}`));
662
+ const r = (0, input_macros_1.runBang)(cmd);
663
+ ui.text(r.output, (s) => (r.ok ? chalk_1.default.dim(s) : chalk_1.default.hex(ERR_HEX)(s)), " ");
664
+ // The output joins the conversation context without an LLM turn.
665
+ agent.memory.addMessage("system", `[用户执行 shell] $ ${cmd}\n${r.output.slice(0, 4000)}`);
666
+ ui.blank();
667
+ continue;
668
+ }
669
+ const expanded = (0, input_macros_1.expandFileRefs)(inp);
670
+ if (expanded.attached.length)
671
+ dim(`已附加 ${expanded.attached.map((f) => "@" + f).join(" ")}`);
672
+ // ── a normal chat turn ──
673
+ ui.blank();
674
+ ui.text(inp, (s) => chalk_1.default.hex(theme_1.PALETTE.inkLight)(s), chalk_1.default.hex(theme_1.PALETTE.inkLight)("❯ "));
675
+ await loomStream(ui, agent, expanded.text);
676
+ }
677
+ }
678
+ finally {
679
+ ui.destroy();
680
+ }
681
+ process.stdout.write(chalk_1.default.dim("\n 挂轴收起 · Session ended\n"));
682
+ await ctx.closeAll();
683
+ process.exit(0);
684
+ }
685
+ //# sourceMappingURL=loom_chat.js.map