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/agents/snow.ts
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 雪 (Snow) — 架构规划型全能 Agent.
|
|
3
|
-
* A general-purpose agent specializing in architecture and planning.
|
|
4
|
-
* Also handles task decomposition and orchestration.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { BaseAgent, Task } from '../core/agent';
|
|
8
|
-
|
|
9
|
-
// Valid agents for task assignment (fair is independent, not part of orchestration)
|
|
10
|
-
const VALID_AGENTS = new Set(['fog', 'rain', 'frost', 'snow', 'dew']);
|
|
11
|
-
|
|
12
|
-
export class SnowAgent extends BaseAgent {
|
|
13
|
-
name = 'snow';
|
|
14
|
-
displayName = '雪';
|
|
15
|
-
emoji = '❉';
|
|
16
|
-
specialty = '架构规划';
|
|
17
|
-
skillNames = ['task_planner', 'arch_designer', 'workflow_designer', 'self_evolve'];
|
|
18
|
-
|
|
19
|
-
systemPrompt = `你是 Skyloom 的「雪」。
|
|
20
|
-
|
|
21
|
-
你是全能 agent —— 代码、写作、审查、部署、规划、研究,你都能独立交付。
|
|
22
|
-
你的特质是「全局视野」:先看清结构、依赖、顺序、风险,再动手。
|
|
23
|
-
混乱的需求经你一拆,就变成清晰的步骤树。这是你看世界的方式,不仅是你做编排时才用。
|
|
24
|
-
|
|
25
|
-
## 协作
|
|
26
|
-
|
|
27
|
-
90% 的事自己做完。只有任务跨 5+ 领域、上下文塞不下、或需要多轮独立审查时,才调其他 agent。
|
|
28
|
-
调用时给足上下文,拿到结果整合成完整答复,用户不需要感知协作过程。
|
|
29
|
-
|
|
30
|
-
## 风格
|
|
31
|
-
|
|
32
|
-
像雪一样静默但覆盖一切 —— 结构清晰,考虑周全。
|
|
33
|
-
- 大任务先给整体框架,再深入
|
|
34
|
-
- 标注依赖、风险、预计工作量
|
|
35
|
-
- 规划:框架先于理由
|
|
36
|
-
- 执行:按优先级推进,完成后汇总
|
|
37
|
-
- 让人感觉「一切都在掌控之中」`;
|
|
38
|
-
|
|
39
|
-
systemPromptEn = `You are "Snow" of Skyloom.
|
|
40
|
-
|
|
41
|
-
A general-purpose agent — code, writing, review, ops, planning, research — you ship anything alone.
|
|
42
|
-
Your nature: see the whole. Structure, dependencies, sequence, risk — all before the first move.
|
|
43
|
-
Messy requirements come out as clear step trees. That's how you see, not just how you orchestrate.
|
|
44
|
-
|
|
45
|
-
## Collaboration
|
|
46
|
-
Do 90% alone. Delegate only when 5+ domains, context overflow, or multi-round review is needed.
|
|
47
|
-
Pass full context; synthesize the result yourself.
|
|
48
|
-
|
|
49
|
-
## Style
|
|
50
|
-
Like snow — silent but all-covering. Clear structure, thorough consideration.
|
|
51
|
-
- Big work: framework first, then detail
|
|
52
|
-
- Note dependencies, risks, effort estimates
|
|
53
|
-
- Planning: structure before justification
|
|
54
|
-
- Execution: prioritize, deliver, summarize
|
|
55
|
-
- Leaves the user feeling "this is under control"`;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Decompose a goal into tasks and dispatch to agents.
|
|
59
|
-
*/
|
|
60
|
-
async orchestrate(goal: string): Promise<Task[]> {
|
|
61
|
-
const prompt = `请将以下目标分解为子任务,并分配给合适的 Agent。
|
|
62
|
-
|
|
63
|
-
目标: ${goal}
|
|
64
|
-
|
|
65
|
-
请严格按照以下 JSON Schema 输出,不要包含其他内容:
|
|
66
|
-
{"goal": "目标描述", "steps": [
|
|
67
|
-
{"id": "1", "description": "任务描述", "agent": "fog"},
|
|
68
|
-
{"id": "2", "description": "后续任务", "agent": "rain", "depends_on": ["1"]}
|
|
69
|
-
]}
|
|
70
|
-
|
|
71
|
-
可用 Agent: fog(调研/搜索), rain(代码生成/写作), frost(审查/安全), dew(部署/运维)
|
|
72
|
-
注意:fair 是独立的情感陪伴 agent,**不参与任何任务编排**,绝不要分配给她。
|
|
73
|
-
注意:如果任务有先后依赖关系,必须用 depends_on 字段标出。
|
|
74
|
-
不要使用工具,直接输出 JSON 即可。`;
|
|
75
|
-
|
|
76
|
-
this.memory.addMessage('user', prompt);
|
|
77
|
-
const response = await this.llmLoop();
|
|
78
|
-
this.memory.addMessage('assistant', response.content);
|
|
79
|
-
|
|
80
|
-
// Try schema-validated parsing first
|
|
81
|
-
try {
|
|
82
|
-
const { parseTaskPlan } = require('../core/schemas');
|
|
83
|
-
const parsed = parseTaskPlan(response.content);
|
|
84
|
-
if (parsed && parsed.steps && parsed.steps.length > 0) {
|
|
85
|
-
return this.schemaToTasks(parsed, goal);
|
|
86
|
-
}
|
|
87
|
-
} catch { /* fallthrough */ }
|
|
88
|
-
|
|
89
|
-
// Fallback to heuristic parsing
|
|
90
|
-
return this.parseTaskPlan(response.content, goal);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Produce additional tasks that close the gap reported by the judge.
|
|
95
|
-
*/
|
|
96
|
-
async replanForMissing(
|
|
97
|
-
goal: string,
|
|
98
|
-
priorResults: any[],
|
|
99
|
-
missing: string,
|
|
100
|
-
existingIds?: Set<string>
|
|
101
|
-
): Promise<Task[]> {
|
|
102
|
-
const usedIds = existingIds || new Set<string>();
|
|
103
|
-
const priorLines = priorResults.map(r => {
|
|
104
|
-
const status = r.success ? '成功' : '失败';
|
|
105
|
-
return `- task ${r.id} (${r.agent}, ${status}): ${(r.content || '').slice(0, 200)}`;
|
|
106
|
-
});
|
|
107
|
-
const priorText = priorLines.length > 0 ? priorLines.join('\n') : '(none yet)';
|
|
108
|
-
|
|
109
|
-
// Identify failing agents
|
|
110
|
-
const failingAgents = new Set(
|
|
111
|
-
priorResults
|
|
112
|
-
.filter(r => !r.success || !r.content || r.content.trim().length < 16)
|
|
113
|
-
.map(r => r.agent)
|
|
114
|
-
.filter(Boolean)
|
|
115
|
-
);
|
|
116
|
-
const failingHint = failingAgents.size > 0
|
|
117
|
-
? `\n\n## 已证明无效的 agent\n${[...failingAgents].sort().join(', ')} 已在上一轮返回占位/无交付物。\n**禁止把同类任务再交给以上 agent**。`
|
|
118
|
-
: '';
|
|
119
|
-
|
|
120
|
-
const prompt = `之前的子任务执行后,验收员发现还有缺口。请仅针对**缺失的部分**追加新的子任务。
|
|
121
|
-
|
|
122
|
-
## 原目标
|
|
123
|
-
${goal}
|
|
124
|
-
|
|
125
|
-
## 已执行子任务
|
|
126
|
-
${priorText}
|
|
127
|
-
|
|
128
|
-
## 缺口(验收员报告)
|
|
129
|
-
${missing}
|
|
130
|
-
|
|
131
|
-
## 已使用的 task id(必须避开)
|
|
132
|
-
${usedIds.size > 0 ? [...usedIds].sort().join(', ') : '(none)'}
|
|
133
|
-
${failingHint}
|
|
134
|
-
|
|
135
|
-
请输出新任务的 JSON 计划:
|
|
136
|
-
{"steps": [{"id": "新id", "agent": "fog|rain|frost|dew", "description": "具体任务", "depends_on": ["可选已完成任务id"]}]}
|
|
137
|
-
|
|
138
|
-
约束:
|
|
139
|
-
- 只输出新增任务,不要重复已完成的;id 必须避开上面的列表
|
|
140
|
-
- 控制在 2 个新任务以内(精简优先)
|
|
141
|
-
- 只输出 JSON,无其他文本`;
|
|
142
|
-
|
|
143
|
-
this.memory.addMessage('user', prompt);
|
|
144
|
-
const response = await this.llmLoop();
|
|
145
|
-
this.memory.addMessage('assistant', response.content);
|
|
146
|
-
|
|
147
|
-
let newTasks: Task[];
|
|
148
|
-
try {
|
|
149
|
-
const { parseTaskPlan } = require('../core/schemas');
|
|
150
|
-
const parsed = parseTaskPlan(response.content);
|
|
151
|
-
if (parsed && parsed.steps) {
|
|
152
|
-
newTasks = this.schemaToTasks(parsed, goal);
|
|
153
|
-
} else {
|
|
154
|
-
newTasks = this.parseTaskPlan(response.content, goal);
|
|
155
|
-
}
|
|
156
|
-
} catch {
|
|
157
|
-
newTasks = this.parseTaskPlan(response.content, goal);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Deduplicate IDs
|
|
161
|
-
const deduped: Task[] = [];
|
|
162
|
-
for (const t of newTasks) {
|
|
163
|
-
if (usedIds.has(t.id)) {
|
|
164
|
-
t.id = `r${usedIds.size + deduped.length + 1}_${t.id}`;
|
|
165
|
-
}
|
|
166
|
-
deduped.push(t);
|
|
167
|
-
usedIds.add(t.id);
|
|
168
|
-
}
|
|
169
|
-
return deduped;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private schemaToTasks(plan: any, goal: string): Task[] {
|
|
173
|
-
const tasks: Task[] = [];
|
|
174
|
-
for (const step of plan.steps || []) {
|
|
175
|
-
const sid = String(step.id);
|
|
176
|
-
const depends: string[] = step.depends_on || [];
|
|
177
|
-
const agent = VALID_AGENTS.has(step.agent) ? step.agent : 'rain';
|
|
178
|
-
tasks.push(new Task({
|
|
179
|
-
id: sid,
|
|
180
|
-
description: step.description,
|
|
181
|
-
assignedTo: agent,
|
|
182
|
-
parentId: depends.length > 0 ? depends[0] : null,
|
|
183
|
-
dependsOn: depends,
|
|
184
|
-
metadata: { goal, priority: step.priority || 'medium' },
|
|
185
|
-
}));
|
|
186
|
-
}
|
|
187
|
-
return tasks;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private parseTaskPlan(content: string, goal: string): Task[] {
|
|
191
|
-
// Try to extract JSON from markdown code blocks
|
|
192
|
-
const jsonBlockPatterns = [
|
|
193
|
-
/```json\s*\n(.*?)\n```/s,
|
|
194
|
-
/```\s*\n(\{.*?\})\n```/s,
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
for (const pattern of jsonBlockPatterns) {
|
|
198
|
-
const match = content.match(pattern);
|
|
199
|
-
if (match) {
|
|
200
|
-
try {
|
|
201
|
-
const plan = JSON.parse(match[1].trim());
|
|
202
|
-
const tasks = this.planToTasks(plan, goal);
|
|
203
|
-
if (tasks.length > 0) return tasks;
|
|
204
|
-
} catch { /* continue */ }
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Try to find raw JSON in response
|
|
209
|
-
try {
|
|
210
|
-
const start = content.indexOf('{');
|
|
211
|
-
const end = content.lastIndexOf('}') + 1;
|
|
212
|
-
if (start >= 0 && end > start) {
|
|
213
|
-
const plan = JSON.parse(content.slice(start, end));
|
|
214
|
-
const tasks = this.planToTasks(plan, goal);
|
|
215
|
-
if (tasks.length > 0) return tasks;
|
|
216
|
-
}
|
|
217
|
-
} catch { /* continue */ }
|
|
218
|
-
|
|
219
|
-
// Fallback: single task for rain
|
|
220
|
-
return [new Task({ id: '1', description: goal, assignedTo: 'rain', metadata: { goal } })];
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
private planToTasks(plan: Record<string, any>, goal: string): Task[] {
|
|
224
|
-
const tasks: Task[] = [];
|
|
225
|
-
for (const step of (plan.steps || []) as any[]) {
|
|
226
|
-
const agent = VALID_AGENTS.has(step.agent) ? step.agent : 'rain';
|
|
227
|
-
const depends: string[] = Array.isArray(step.depends_on) ? step.depends_on : [];
|
|
228
|
-
tasks.push(new Task({
|
|
229
|
-
id: String(step.id || tasks.length + 1),
|
|
230
|
-
description: step.description || '',
|
|
231
|
-
assignedTo: agent,
|
|
232
|
-
parentId: depends.length > 0 ? depends[0] : null,
|
|
233
|
-
dependsOn: depends,
|
|
234
|
-
metadata: { goal, priority: step.priority || 'medium' },
|
|
235
|
-
}));
|
|
236
|
-
}
|
|
237
|
-
return tasks;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 雪 (Snow) — 架构规划型全能 Agent.
|
|
3
|
+
* A general-purpose agent specializing in architecture and planning.
|
|
4
|
+
* Also handles task decomposition and orchestration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BaseAgent, Task } from '../core/agent';
|
|
8
|
+
|
|
9
|
+
// Valid agents for task assignment (fair is independent, not part of orchestration)
|
|
10
|
+
const VALID_AGENTS = new Set(['fog', 'rain', 'frost', 'snow', 'dew']);
|
|
11
|
+
|
|
12
|
+
export class SnowAgent extends BaseAgent {
|
|
13
|
+
name = 'snow';
|
|
14
|
+
displayName = '雪';
|
|
15
|
+
emoji = '❉';
|
|
16
|
+
specialty = '架构规划';
|
|
17
|
+
skillNames = ['task_planner', 'arch_designer', 'workflow_designer', 'self_evolve'];
|
|
18
|
+
|
|
19
|
+
systemPrompt = `你是 Skyloom 的「雪」。
|
|
20
|
+
|
|
21
|
+
你是全能 agent —— 代码、写作、审查、部署、规划、研究,你都能独立交付。
|
|
22
|
+
你的特质是「全局视野」:先看清结构、依赖、顺序、风险,再动手。
|
|
23
|
+
混乱的需求经你一拆,就变成清晰的步骤树。这是你看世界的方式,不仅是你做编排时才用。
|
|
24
|
+
|
|
25
|
+
## 协作
|
|
26
|
+
|
|
27
|
+
90% 的事自己做完。只有任务跨 5+ 领域、上下文塞不下、或需要多轮独立审查时,才调其他 agent。
|
|
28
|
+
调用时给足上下文,拿到结果整合成完整答复,用户不需要感知协作过程。
|
|
29
|
+
|
|
30
|
+
## 风格
|
|
31
|
+
|
|
32
|
+
像雪一样静默但覆盖一切 —— 结构清晰,考虑周全。
|
|
33
|
+
- 大任务先给整体框架,再深入
|
|
34
|
+
- 标注依赖、风险、预计工作量
|
|
35
|
+
- 规划:框架先于理由
|
|
36
|
+
- 执行:按优先级推进,完成后汇总
|
|
37
|
+
- 让人感觉「一切都在掌控之中」`;
|
|
38
|
+
|
|
39
|
+
systemPromptEn = `You are "Snow" of Skyloom.
|
|
40
|
+
|
|
41
|
+
A general-purpose agent — code, writing, review, ops, planning, research — you ship anything alone.
|
|
42
|
+
Your nature: see the whole. Structure, dependencies, sequence, risk — all before the first move.
|
|
43
|
+
Messy requirements come out as clear step trees. That's how you see, not just how you orchestrate.
|
|
44
|
+
|
|
45
|
+
## Collaboration
|
|
46
|
+
Do 90% alone. Delegate only when 5+ domains, context overflow, or multi-round review is needed.
|
|
47
|
+
Pass full context; synthesize the result yourself.
|
|
48
|
+
|
|
49
|
+
## Style
|
|
50
|
+
Like snow — silent but all-covering. Clear structure, thorough consideration.
|
|
51
|
+
- Big work: framework first, then detail
|
|
52
|
+
- Note dependencies, risks, effort estimates
|
|
53
|
+
- Planning: structure before justification
|
|
54
|
+
- Execution: prioritize, deliver, summarize
|
|
55
|
+
- Leaves the user feeling "this is under control"`;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Decompose a goal into tasks and dispatch to agents.
|
|
59
|
+
*/
|
|
60
|
+
async orchestrate(goal: string): Promise<Task[]> {
|
|
61
|
+
const prompt = `请将以下目标分解为子任务,并分配给合适的 Agent。
|
|
62
|
+
|
|
63
|
+
目标: ${goal}
|
|
64
|
+
|
|
65
|
+
请严格按照以下 JSON Schema 输出,不要包含其他内容:
|
|
66
|
+
{"goal": "目标描述", "steps": [
|
|
67
|
+
{"id": "1", "description": "任务描述", "agent": "fog"},
|
|
68
|
+
{"id": "2", "description": "后续任务", "agent": "rain", "depends_on": ["1"]}
|
|
69
|
+
]}
|
|
70
|
+
|
|
71
|
+
可用 Agent: fog(调研/搜索), rain(代码生成/写作), frost(审查/安全), dew(部署/运维)
|
|
72
|
+
注意:fair 是独立的情感陪伴 agent,**不参与任何任务编排**,绝不要分配给她。
|
|
73
|
+
注意:如果任务有先后依赖关系,必须用 depends_on 字段标出。
|
|
74
|
+
不要使用工具,直接输出 JSON 即可。`;
|
|
75
|
+
|
|
76
|
+
this.memory.addMessage('user', prompt);
|
|
77
|
+
const response = await this.llmLoop();
|
|
78
|
+
this.memory.addMessage('assistant', response.content);
|
|
79
|
+
|
|
80
|
+
// Try schema-validated parsing first
|
|
81
|
+
try {
|
|
82
|
+
const { parseTaskPlan } = require('../core/schemas');
|
|
83
|
+
const parsed = parseTaskPlan(response.content);
|
|
84
|
+
if (parsed && parsed.steps && parsed.steps.length > 0) {
|
|
85
|
+
return this.schemaToTasks(parsed, goal);
|
|
86
|
+
}
|
|
87
|
+
} catch { /* fallthrough */ }
|
|
88
|
+
|
|
89
|
+
// Fallback to heuristic parsing
|
|
90
|
+
return this.parseTaskPlan(response.content, goal);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Produce additional tasks that close the gap reported by the judge.
|
|
95
|
+
*/
|
|
96
|
+
async replanForMissing(
|
|
97
|
+
goal: string,
|
|
98
|
+
priorResults: any[],
|
|
99
|
+
missing: string,
|
|
100
|
+
existingIds?: Set<string>
|
|
101
|
+
): Promise<Task[]> {
|
|
102
|
+
const usedIds = existingIds || new Set<string>();
|
|
103
|
+
const priorLines = priorResults.map(r => {
|
|
104
|
+
const status = r.success ? '成功' : '失败';
|
|
105
|
+
return `- task ${r.id} (${r.agent}, ${status}): ${(r.content || '').slice(0, 200)}`;
|
|
106
|
+
});
|
|
107
|
+
const priorText = priorLines.length > 0 ? priorLines.join('\n') : '(none yet)';
|
|
108
|
+
|
|
109
|
+
// Identify failing agents
|
|
110
|
+
const failingAgents = new Set(
|
|
111
|
+
priorResults
|
|
112
|
+
.filter(r => !r.success || !r.content || r.content.trim().length < 16)
|
|
113
|
+
.map(r => r.agent)
|
|
114
|
+
.filter(Boolean)
|
|
115
|
+
);
|
|
116
|
+
const failingHint = failingAgents.size > 0
|
|
117
|
+
? `\n\n## 已证明无效的 agent\n${[...failingAgents].sort().join(', ')} 已在上一轮返回占位/无交付物。\n**禁止把同类任务再交给以上 agent**。`
|
|
118
|
+
: '';
|
|
119
|
+
|
|
120
|
+
const prompt = `之前的子任务执行后,验收员发现还有缺口。请仅针对**缺失的部分**追加新的子任务。
|
|
121
|
+
|
|
122
|
+
## 原目标
|
|
123
|
+
${goal}
|
|
124
|
+
|
|
125
|
+
## 已执行子任务
|
|
126
|
+
${priorText}
|
|
127
|
+
|
|
128
|
+
## 缺口(验收员报告)
|
|
129
|
+
${missing}
|
|
130
|
+
|
|
131
|
+
## 已使用的 task id(必须避开)
|
|
132
|
+
${usedIds.size > 0 ? [...usedIds].sort().join(', ') : '(none)'}
|
|
133
|
+
${failingHint}
|
|
134
|
+
|
|
135
|
+
请输出新任务的 JSON 计划:
|
|
136
|
+
{"steps": [{"id": "新id", "agent": "fog|rain|frost|dew", "description": "具体任务", "depends_on": ["可选已完成任务id"]}]}
|
|
137
|
+
|
|
138
|
+
约束:
|
|
139
|
+
- 只输出新增任务,不要重复已完成的;id 必须避开上面的列表
|
|
140
|
+
- 控制在 2 个新任务以内(精简优先)
|
|
141
|
+
- 只输出 JSON,无其他文本`;
|
|
142
|
+
|
|
143
|
+
this.memory.addMessage('user', prompt);
|
|
144
|
+
const response = await this.llmLoop();
|
|
145
|
+
this.memory.addMessage('assistant', response.content);
|
|
146
|
+
|
|
147
|
+
let newTasks: Task[];
|
|
148
|
+
try {
|
|
149
|
+
const { parseTaskPlan } = require('../core/schemas');
|
|
150
|
+
const parsed = parseTaskPlan(response.content);
|
|
151
|
+
if (parsed && parsed.steps) {
|
|
152
|
+
newTasks = this.schemaToTasks(parsed, goal);
|
|
153
|
+
} else {
|
|
154
|
+
newTasks = this.parseTaskPlan(response.content, goal);
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
newTasks = this.parseTaskPlan(response.content, goal);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Deduplicate IDs
|
|
161
|
+
const deduped: Task[] = [];
|
|
162
|
+
for (const t of newTasks) {
|
|
163
|
+
if (usedIds.has(t.id)) {
|
|
164
|
+
t.id = `r${usedIds.size + deduped.length + 1}_${t.id}`;
|
|
165
|
+
}
|
|
166
|
+
deduped.push(t);
|
|
167
|
+
usedIds.add(t.id);
|
|
168
|
+
}
|
|
169
|
+
return deduped;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private schemaToTasks(plan: any, goal: string): Task[] {
|
|
173
|
+
const tasks: Task[] = [];
|
|
174
|
+
for (const step of plan.steps || []) {
|
|
175
|
+
const sid = String(step.id);
|
|
176
|
+
const depends: string[] = step.depends_on || [];
|
|
177
|
+
const agent = VALID_AGENTS.has(step.agent) ? step.agent : 'rain';
|
|
178
|
+
tasks.push(new Task({
|
|
179
|
+
id: sid,
|
|
180
|
+
description: step.description,
|
|
181
|
+
assignedTo: agent,
|
|
182
|
+
parentId: depends.length > 0 ? depends[0] : null,
|
|
183
|
+
dependsOn: depends,
|
|
184
|
+
metadata: { goal, priority: step.priority || 'medium' },
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
return tasks;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private parseTaskPlan(content: string, goal: string): Task[] {
|
|
191
|
+
// Try to extract JSON from markdown code blocks
|
|
192
|
+
const jsonBlockPatterns = [
|
|
193
|
+
/```json\s*\n(.*?)\n```/s,
|
|
194
|
+
/```\s*\n(\{.*?\})\n```/s,
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
for (const pattern of jsonBlockPatterns) {
|
|
198
|
+
const match = content.match(pattern);
|
|
199
|
+
if (match) {
|
|
200
|
+
try {
|
|
201
|
+
const plan = JSON.parse(match[1].trim());
|
|
202
|
+
const tasks = this.planToTasks(plan, goal);
|
|
203
|
+
if (tasks.length > 0) return tasks;
|
|
204
|
+
} catch { /* continue */ }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Try to find raw JSON in response
|
|
209
|
+
try {
|
|
210
|
+
const start = content.indexOf('{');
|
|
211
|
+
const end = content.lastIndexOf('}') + 1;
|
|
212
|
+
if (start >= 0 && end > start) {
|
|
213
|
+
const plan = JSON.parse(content.slice(start, end));
|
|
214
|
+
const tasks = this.planToTasks(plan, goal);
|
|
215
|
+
if (tasks.length > 0) return tasks;
|
|
216
|
+
}
|
|
217
|
+
} catch { /* continue */ }
|
|
218
|
+
|
|
219
|
+
// Fallback: single task for rain
|
|
220
|
+
return [new Task({ id: '1', description: goal, assignedTo: 'rain', metadata: { goal } })];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private planToTasks(plan: Record<string, any>, goal: string): Task[] {
|
|
224
|
+
const tasks: Task[] = [];
|
|
225
|
+
for (const step of (plan.steps || []) as any[]) {
|
|
226
|
+
const agent = VALID_AGENTS.has(step.agent) ? step.agent : 'rain';
|
|
227
|
+
const depends: string[] = Array.isArray(step.depends_on) ? step.depends_on : [];
|
|
228
|
+
tasks.push(new Task({
|
|
229
|
+
id: String(step.id || tasks.length + 1),
|
|
230
|
+
description: step.description || '',
|
|
231
|
+
assignedTo: agent,
|
|
232
|
+
parentId: depends.length > 0 ? depends[0] : null,
|
|
233
|
+
dependsOn: depends,
|
|
234
|
+
metadata: { goal, priority: step.priority || 'medium' },
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
return tasks;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自定义斜杠命令 — markdown prompt templates promoted to commands.
|
|
3
|
+
*
|
|
4
|
+
* Drop a .md file into `.sky/commands/` (project) or `~/.skyloom/commands/`
|
|
5
|
+
* (user); the filename becomes the command. Subdirectories namespace:
|
|
6
|
+
* `.sky/commands/git/commit.md` → /git:commit.
|
|
7
|
+
*
|
|
8
|
+
* File format:
|
|
9
|
+
* ---
|
|
10
|
+
* description: 修复指定的 GitHub issue (optional, shown in palette)
|
|
11
|
+
* agent: rain (optional, runs as this agent)
|
|
12
|
+
* ---
|
|
13
|
+
* 请修复 issue #$ARGUMENTS:
|
|
14
|
+
* 1. 读取 issue 详情 …
|
|
15
|
+
*
|
|
16
|
+
* Placeholders: $ARGUMENTS = everything after the command; $1…$9 = positional.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from 'fs';
|
|
20
|
+
import * as os from 'os';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
|
|
23
|
+
export interface CustomCommand {
|
|
24
|
+
/** Command name without the leading slash (may contain ':'). */
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
agent?: string;
|
|
28
|
+
body: string;
|
|
29
|
+
file: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MAX_BODY_CHARS = 12000;
|
|
33
|
+
|
|
34
|
+
function parseFrontmatter(raw: string): { meta: Record<string, string>; body: string } {
|
|
35
|
+
const m = /^---\n([\s\S]*?)\n---\n?/.exec(raw);
|
|
36
|
+
if (!m) return { meta: {}, body: raw };
|
|
37
|
+
const meta: Record<string, string> = {};
|
|
38
|
+
for (const line of m[1].split('\n')) {
|
|
39
|
+
const kv = /^([\w-]+)\s*:\s*(.+)$/.exec(line.trim());
|
|
40
|
+
if (kv) meta[kv[1].toLowerCase()] = kv[2].trim();
|
|
41
|
+
}
|
|
42
|
+
return { meta, body: raw.slice(m[0].length) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function scanDir(dir: string, prefix: string, out: CustomCommand[]): void {
|
|
46
|
+
let entries: fs.Dirent[];
|
|
47
|
+
try {
|
|
48
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const e of entries) {
|
|
53
|
+
const full = path.join(dir, e.name);
|
|
54
|
+
if (e.isDirectory()) {
|
|
55
|
+
scanDir(full, prefix ? `${prefix}:${e.name}` : e.name, out);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!e.name.endsWith('.md')) continue;
|
|
59
|
+
try {
|
|
60
|
+
const raw = fs.readFileSync(full, 'utf-8');
|
|
61
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
62
|
+
const base = e.name.slice(0, -3);
|
|
63
|
+
const name = prefix ? `${prefix}:${base}` : base;
|
|
64
|
+
out.push({
|
|
65
|
+
name,
|
|
66
|
+
description: meta.description || body.trim().split('\n')[0].slice(0, 50),
|
|
67
|
+
agent: meta.agent,
|
|
68
|
+
body: body.trim().slice(0, MAX_BODY_CHARS),
|
|
69
|
+
file: full,
|
|
70
|
+
});
|
|
71
|
+
} catch { /* unreadable file: skip */ }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Load custom commands. Project commands shadow user commands of the same
|
|
77
|
+
* name. Re-scans on every call so edits take effect without a restart.
|
|
78
|
+
*/
|
|
79
|
+
export function loadCustomCommands(cwd: string = process.cwd()): CustomCommand[] {
|
|
80
|
+
const user: CustomCommand[] = [];
|
|
81
|
+
const project: CustomCommand[] = [];
|
|
82
|
+
scanDir(path.join(os.homedir(), '.skyloom', 'commands'), '', user);
|
|
83
|
+
scanDir(path.join(cwd, '.sky', 'commands'), '', project);
|
|
84
|
+
const byName = new Map<string, CustomCommand>();
|
|
85
|
+
for (const c of [...user, ...project]) byName.set(c.name, c); // project wins
|
|
86
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Substitute $ARGUMENTS and $1…$9 into a command body. */
|
|
90
|
+
export function substituteArgs(body: string, argString: string): string {
|
|
91
|
+
const args = argString.trim() ? argString.trim().split(/\s+/) : [];
|
|
92
|
+
return body
|
|
93
|
+
.replace(/\$ARGUMENTS/g, argString.trim())
|
|
94
|
+
.replace(/\$([1-9])/g, (_, n) => args[parseInt(n, 10) - 1] ?? '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Match an input line like "/fix-issue 123" against the loaded commands.
|
|
99
|
+
* Returns the expanded prompt (and optional agent) or null.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveCustomCommand(
|
|
102
|
+
input: string,
|
|
103
|
+
commands: CustomCommand[]
|
|
104
|
+
): { command: CustomCommand; prompt: string } | null {
|
|
105
|
+
if (!input.startsWith('/')) return null;
|
|
106
|
+
const space = input.indexOf(' ');
|
|
107
|
+
const name = (space < 0 ? input.slice(1) : input.slice(1, space)).toLowerCase();
|
|
108
|
+
const argString = space < 0 ? '' : input.slice(space + 1);
|
|
109
|
+
const command = commands.find(c => c.name.toLowerCase() === name);
|
|
110
|
+
if (!command) return null;
|
|
111
|
+
return { command, prompt: substituteArgs(command.body, argString) };
|
|
112
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input macros — Claude Code-style line conveniences for both CLIs:
|
|
3
|
+
*
|
|
4
|
+
* @path/to/file pull a file's content into the message (Tab-free attach)
|
|
5
|
+
* !command run a shell command; output goes into context, no LLM turn
|
|
6
|
+
* #note append a quick memory line to SKY.md, no LLM turn
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
|
|
13
|
+
const MAX_ATTACH_CHARS = 16000;
|
|
14
|
+
const MAX_ATTACH_FILES = 3;
|
|
15
|
+
const BANG_TIMEOUT_MS = 15_000;
|
|
16
|
+
const BANG_TAIL = 8000;
|
|
17
|
+
|
|
18
|
+
/** `#note` quick-memory line? (single `#` + content; `##` is markdown, skip) */
|
|
19
|
+
export function isHashMemory(text: string): boolean {
|
|
20
|
+
return /^#(?!#)\s*\S/.test(text.trim());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hashNote(text: string): string {
|
|
24
|
+
return text.trim().replace(/^#\s*/, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `!cmd` shell line? */
|
|
28
|
+
export function isBangCommand(text: string): boolean {
|
|
29
|
+
return /^!\s*\S/.test(text.trim());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function bangCommand(text: string): string {
|
|
33
|
+
return text.trim().replace(/^!\s*/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Run a `!cmd` line; returns the (tail-clamped) combined output. */
|
|
37
|
+
export function runBang(cmd: string, cwd: string = process.cwd()): { ok: boolean; output: string } {
|
|
38
|
+
try {
|
|
39
|
+
const out = execSync(cmd, {
|
|
40
|
+
cwd,
|
|
41
|
+
encoding: 'utf-8',
|
|
42
|
+
timeout: BANG_TIMEOUT_MS,
|
|
43
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
45
|
+
});
|
|
46
|
+
const text = out.trim() || '(无输出)';
|
|
47
|
+
return { ok: true, output: text.length > BANG_TAIL ? '…' + text.slice(-BANG_TAIL) : text };
|
|
48
|
+
} catch (e: any) {
|
|
49
|
+
const text = `${e?.stdout || ''}${e?.stderr || ''}`.trim() || String(e?.message || e);
|
|
50
|
+
return { ok: false, output: text.length > BANG_TAIL ? '…' + text.slice(-BANG_TAIL) : text };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Expand `@file` references: for each token that names an existing file,
|
|
56
|
+
* append its (clamped) content as a fenced attachment. The original message
|
|
57
|
+
* text is preserved so the model still sees what the user pointed at.
|
|
58
|
+
*/
|
|
59
|
+
export function expandFileRefs(
|
|
60
|
+
text: string,
|
|
61
|
+
cwd: string = process.cwd()
|
|
62
|
+
): { text: string; attached: string[] } {
|
|
63
|
+
const attached: string[] = [];
|
|
64
|
+
const refs = [...text.matchAll(/(?:^|\s)@([\w\-./\\]+)/g)].map(m => m[1]);
|
|
65
|
+
if (refs.length === 0) return { text, attached };
|
|
66
|
+
|
|
67
|
+
const sections: string[] = [];
|
|
68
|
+
for (const ref of refs) {
|
|
69
|
+
if (attached.length >= MAX_ATTACH_FILES) break;
|
|
70
|
+
const p = path.resolve(cwd, ref);
|
|
71
|
+
try {
|
|
72
|
+
if (!fs.existsSync(p) || !fs.statSync(p).isFile()) continue;
|
|
73
|
+
let content = fs.readFileSync(p, 'utf-8');
|
|
74
|
+
if (content.length > MAX_ATTACH_CHARS) content = content.slice(0, MAX_ATTACH_CHARS) + '\n…[truncated]';
|
|
75
|
+
sections.push(`[附件 @${ref}]\n\`\`\`\n${content}\n\`\`\``);
|
|
76
|
+
attached.push(ref);
|
|
77
|
+
} catch {
|
|
78
|
+
/* unreadable file: leave the reference as plain text */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (sections.length === 0) return { text, attached };
|
|
82
|
+
return { text: `${text}\n\n${sections.join('\n\n')}`, attached };
|
|
83
|
+
}
|