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.
Files changed (193) 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.d.ts +20 -0
  49. package/dist/core/agent.d.ts.map +1 -1
  50. package/dist/core/agent.js +199 -16
  51. package/dist/core/agent.js.map +1 -1
  52. package/dist/core/factory.d.ts.map +1 -1
  53. package/dist/core/factory.js +34 -2
  54. package/dist/core/factory.js.map +1 -1
  55. package/dist/core/file_checkpoint.d.ts +57 -0
  56. package/dist/core/file_checkpoint.d.ts.map +1 -0
  57. package/dist/core/file_checkpoint.js +162 -0
  58. package/dist/core/file_checkpoint.js.map +1 -0
  59. package/dist/core/hooks.d.ts +43 -0
  60. package/dist/core/hooks.d.ts.map +1 -0
  61. package/dist/core/hooks.js +110 -0
  62. package/dist/core/hooks.js.map +1 -0
  63. package/dist/core/llm.d.ts.map +1 -1
  64. package/dist/core/llm.js +15 -9
  65. package/dist/core/llm.js.map +1 -1
  66. package/dist/core/longdoc.js +5 -5
  67. package/dist/core/mcp.d.ts +16 -0
  68. package/dist/core/mcp.d.ts.map +1 -1
  69. package/dist/core/mcp.js +55 -0
  70. package/dist/core/mcp.js.map +1 -1
  71. package/dist/core/model_config.d.ts +40 -0
  72. package/dist/core/model_config.d.ts.map +1 -0
  73. package/dist/core/model_config.js +191 -0
  74. package/dist/core/model_config.js.map +1 -0
  75. package/dist/core/skill.d.ts +7 -0
  76. package/dist/core/skill.d.ts.map +1 -1
  77. package/dist/core/skill.js +47 -0
  78. package/dist/core/skill.js.map +1 -1
  79. package/dist/core/skymd.d.ts +39 -0
  80. package/dist/core/skymd.d.ts.map +1 -0
  81. package/dist/core/skymd.js +177 -0
  82. package/dist/core/skymd.js.map +1 -0
  83. package/dist/core/tool.d.ts +12 -0
  84. package/dist/core/tool.d.ts.map +1 -1
  85. package/dist/core/tool.js +30 -0
  86. package/dist/core/tool.js.map +1 -1
  87. package/dist/core/verify.d.ts +27 -0
  88. package/dist/core/verify.d.ts.map +1 -0
  89. package/dist/core/verify.js +62 -0
  90. package/dist/core/verify.js.map +1 -0
  91. package/dist/skills/loader.d.ts +22 -2
  92. package/dist/skills/loader.d.ts.map +1 -1
  93. package/dist/skills/loader.js +45 -15
  94. package/dist/skills/loader.js.map +1 -1
  95. package/dist/tools/builtin.d.ts.map +1 -1
  96. package/dist/tools/builtin.js +13 -3
  97. package/dist/tools/builtin.js.map +1 -1
  98. package/dist/tools/model_tool.d.ts +11 -0
  99. package/dist/tools/model_tool.d.ts.map +1 -0
  100. package/dist/tools/model_tool.js +71 -0
  101. package/dist/tools/model_tool.js.map +1 -0
  102. package/dist/tools/todo.d.ts +30 -0
  103. package/dist/tools/todo.d.ts.map +1 -0
  104. package/dist/tools/todo.js +78 -0
  105. package/dist/tools/todo.js.map +1 -0
  106. package/docs/AESTHETIC_DESIGN.md +152 -144
  107. package/docs/OPTIMIZATION_PLAN.md +178 -178
  108. package/package.json +68 -68
  109. package/scripts/install.js +48 -48
  110. package/scripts/link.js +10 -10
  111. package/setup.bat +79 -79
  112. package/skill-test-ty2fOA/test.md +10 -10
  113. package/src/agents/dew.ts +70 -70
  114. package/src/agents/fair.ts +102 -102
  115. package/src/agents/fog.ts +48 -48
  116. package/src/agents/frost.ts +50 -50
  117. package/src/agents/rain.ts +50 -50
  118. package/src/agents/snow.ts +239 -239
  119. package/src/cli/commands_md.ts +112 -0
  120. package/src/cli/input_macros.ts +83 -0
  121. package/src/cli/loom.ts +982 -0
  122. package/src/cli/loom_chat.ts +598 -0
  123. package/src/cli/main.ts +255 -9
  124. package/src/cli/mode.ts +58 -58
  125. package/src/cli/tui.ts +228 -222
  126. package/src/core/agent/guard.ts +134 -134
  127. package/src/core/agent/task.ts +100 -100
  128. package/src/core/agent.ts +195 -16
  129. package/src/core/arbitrate.ts +162 -162
  130. package/src/core/catalog.ts +178 -178
  131. package/src/core/checkpoint.ts +94 -94
  132. package/src/core/estimate.ts +104 -104
  133. package/src/core/evolve.ts +191 -191
  134. package/src/core/factory.ts +31 -2
  135. package/src/core/file_checkpoint.ts +136 -0
  136. package/src/core/filter.ts +103 -103
  137. package/src/core/graph.ts +156 -156
  138. package/src/core/hooks.ts +126 -0
  139. package/src/core/icons.ts +53 -53
  140. package/src/core/index.ts +37 -37
  141. package/src/core/learn.ts +146 -146
  142. package/src/core/llm.ts +15 -9
  143. package/src/core/longdoc.ts +155 -155
  144. package/src/core/mcp.ts +48 -0
  145. package/src/core/mcp_server.ts +176 -176
  146. package/src/core/model_config.ts +157 -0
  147. package/src/core/profile.ts +255 -255
  148. package/src/core/router.ts +124 -124
  149. package/src/core/sandbox.ts +142 -142
  150. package/src/core/security.ts +243 -243
  151. package/src/core/skill.ts +42 -0
  152. package/src/core/skymd.ts +143 -0
  153. package/src/core/theme.ts +65 -65
  154. package/src/core/tool.ts +30 -0
  155. package/src/core/tool_router.ts +193 -193
  156. package/src/core/vector.ts +152 -152
  157. package/src/core/verify.ts +71 -0
  158. package/src/core/workspace.ts +150 -150
  159. package/src/plugins/loader.ts +66 -66
  160. package/src/skills/loader.ts +45 -16
  161. package/src/sql.js.d.ts +29 -29
  162. package/src/tools/builtin.ts +13 -3
  163. package/src/tools/computer.ts +269 -269
  164. package/src/tools/delegate.ts +49 -49
  165. package/src/tools/model_tool.ts +74 -0
  166. package/src/tools/todo.ts +76 -0
  167. package/src/web/tts.ts +93 -93
  168. package/tests/agent.test.ts +159 -159
  169. package/tests/agent_helpers.test.ts +48 -48
  170. package/tests/bus.test.ts +121 -121
  171. package/tests/catalog.test.ts +86 -86
  172. package/tests/checkpoint_commands.test.ts +124 -0
  173. package/tests/claude_compat.test.ts +110 -0
  174. package/tests/config.test.ts +41 -41
  175. package/tests/guard.test.ts +75 -75
  176. package/tests/icons.test.ts +45 -45
  177. package/tests/loom.test.ts +248 -0
  178. package/tests/memory.test.ts +170 -170
  179. package/tests/model_config.test.ts +109 -0
  180. package/tests/router.test.ts +86 -86
  181. package/tests/schemas.test.ts +51 -51
  182. package/tests/semantic.test.ts +83 -83
  183. package/tests/setup.ts +10 -10
  184. package/tests/skill.test.ts +172 -172
  185. package/tests/skymd.test.ts +146 -0
  186. package/tests/task.test.ts +60 -60
  187. package/tests/todo_toolstats.test.ts +94 -0
  188. package/tests/tool.test.ts +108 -108
  189. package/tests/tool_router.test.ts +71 -71
  190. package/tests/tui.test.ts +67 -67
  191. package/vitest.config.ts +17 -17
  192. package/=12 +0 -0
  193. package/=8 +0 -0
@@ -1,176 +1,176 @@
1
- /**
2
- * MCP server — expose Skyloom agents as tools for external MCP clients.
3
- *
4
- * Runs over stdio (JSON-RPC 2.0). Any MCP-compatible client (Claude Desktop,
5
- * Zed, Continue, etc.) can connect and call Skyloom agents as tools.
6
- *
7
- * Registered tools:
8
- * - mcp_chat — single-turn question to any agent
9
- * - mcp_task — multi-agent orchestration
10
- * - list_agents — return available agent names + specialties
11
- */
12
-
13
- import * as readline from 'readline';
14
- import { createSystemContext, orchestrateTask } from './factory';
15
-
16
- const MCP_VERSION = '2025-03-26';
17
- const SERVER_INFO = { name: 'skyloom', version: '1.4.0' };
18
-
19
- const TOOL_DEFS = [
20
- {
21
- name: 'mcp_chat',
22
- description: 'Ask a Skyloom agent a question. Use fair for companionship, fog for research, rain for code, frost for review, dew for ops, snow for planning.',
23
- inputSchema: {
24
- type: 'object',
25
- properties: {
26
- agent: { type: 'string', description: 'Agent name: fog/rain/frost/snow/dew/fair (default: fair)' },
27
- message: { type: 'string', description: 'What to ask the agent' },
28
- },
29
- required: ['message'],
30
- },
31
- },
32
- {
33
- name: 'mcp_task',
34
- description: 'Run multi-agent orchestration. Snow plans, then agents collaborate. Use for complex multi-step goals.',
35
- inputSchema: {
36
- type: 'object',
37
- properties: {
38
- goal: { type: 'string', description: 'The goal to accomplish' },
39
- },
40
- required: ['goal'],
41
- },
42
- },
43
- {
44
- name: 'list_agents',
45
- description: 'List available Skyloom agents and their specialties.',
46
- inputSchema: { type: 'object', properties: {}, required: [] },
47
- },
48
- ];
49
-
50
- export class MCPServer {
51
- private initialized = false;
52
- private ctx: any = null;
53
- private agents: Map<string, any> = new Map();
54
-
55
- async run(): Promise<void> {
56
- const rl = readline.createInterface({ input: process.stdin });
57
- for await (const line of rl) {
58
- const trimmed = line.trim();
59
- if (!trimmed) continue;
60
- try {
61
- const msg = JSON.parse(trimmed);
62
- const resp = await this.handle(msg);
63
- if (resp !== null) {
64
- process.stdout.write(JSON.stringify(resp) + '\n');
65
- }
66
- } catch {
67
- // Ignore parse errors
68
- }
69
- }
70
- }
71
-
72
- private async handle(msg: Record<string, any>): Promise<Record<string, any> | null> {
73
- const mid = msg.id;
74
- const method = msg.method || '';
75
- const params = msg.params || {};
76
-
77
- // Notifications — no response
78
- if (method.startsWith('notifications/')) return null;
79
-
80
- try {
81
- let result: any;
82
- if (method === 'initialize') {
83
- result = await this.init();
84
- } else if (method === 'tools/list') {
85
- result = { tools: TOOL_DEFS };
86
- } else if (method === 'tools/call') {
87
- result = await this.toolsCall(params);
88
- } else {
89
- return { jsonrpc: '2.0', id: mid, error: { code: -32601, message: `unknown method: ${method}` } };
90
- }
91
- return { jsonrpc: '2.0', id: mid, result };
92
- } catch (e: any) {
93
- return { jsonrpc: '2.0', id: mid, error: { code: -32603, message: e.message || String(e) } };
94
- }
95
- }
96
-
97
- private async init(): Promise<Record<string, any>> {
98
- this.initialized = true;
99
- return { protocolVersion: MCP_VERSION, capabilities: { tools: {} }, serverInfo: SERVER_INFO };
100
- }
101
-
102
- private async ensureCtx(): Promise<void> {
103
- if (!this.ctx) {
104
- this.ctx = createSystemContext();
105
- await this.ctx.initAll();
106
- this.agents = this.ctx.agentMap;
107
- }
108
- }
109
-
110
- private async toolsCall(params: Record<string, any>): Promise<Record<string, any>> {
111
- const name = params.name || '';
112
- const args = params.arguments || {};
113
-
114
- if (name === 'list_agents') return this.handleListAgents();
115
- if (name === 'mcp_chat') return this.handleChat(args);
116
- if (name === 'mcp_task') return this.handleTask(args);
117
- return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
118
- }
119
-
120
- private async handleListAgents(): Promise<Record<string, any>> {
121
- await this.ensureCtx();
122
- const lines = ['Available agents:'];
123
- for (const [name, agent] of this.agents) {
124
- lines.push(`- ${name} (${agent.displayName}): ${agent.specialty}`);
125
- }
126
- return { content: [{ type: 'text', text: lines.join('\n') }] };
127
- }
128
-
129
- private async handleChat(args: Record<string, any>): Promise<Record<string, any>> {
130
- const agentName = String(args.agent || 'fair').trim().toLowerCase();
131
- const message = String(args.message || '').trim();
132
- if (!message) {
133
- return { content: [{ type: 'text', text: 'Error: message is required' }], isError: true };
134
- }
135
-
136
- await this.ensureCtx();
137
- const agent = this.agents.get(agentName);
138
- if (!agent) {
139
- return { content: [{ type: 'text', text: `Unknown agent '${agentName}'. Available: ${[...this.agents.keys()].join(', ')}` }], isError: true };
140
- }
141
-
142
- try {
143
- const reply = await agent.chat(message);
144
- return { content: [{ type: 'text', text: reply }] };
145
- } catch (e: any) {
146
- return { content: [{ type: 'text', text: `Agent error: ${e.message || e}` }], isError: true };
147
- }
148
- }
149
-
150
- private async handleTask(args: Record<string, any>): Promise<Record<string, any>> {
151
- const goal = String(args.goal || '').trim();
152
- if (!goal) {
153
- return { content: [{ type: 'text', text: 'Error: goal is required' }], isError: true };
154
- }
155
-
156
- await this.ensureCtx();
157
- try {
158
- const [_tasks, results, summary] = await orchestrateTask(goal, this.agents);
159
- const ok = results.filter(r => r.success).length;
160
- const total = results.length;
161
- const contentLines = [`[${ok}/${total} tasks completed]`];
162
- for (const r of results) {
163
- contentLines.push(`## ${r.agent}: ${r.description}\n${r.content || '(no content)'}`);
164
- }
165
- if (summary) contentLines.push(`\n${summary}`);
166
- return { content: [{ type: 'text', text: contentLines.join('\n') }] };
167
- } catch (e: any) {
168
- return { content: [{ type: 'text', text: `Task error: ${e.message || e}` }], isError: true };
169
- }
170
- }
171
- }
172
-
173
- export async function startMCPServer(): Promise<void> {
174
- const server = new MCPServer();
175
- await server.run();
176
- }
1
+ /**
2
+ * MCP server — expose Skyloom agents as tools for external MCP clients.
3
+ *
4
+ * Runs over stdio (JSON-RPC 2.0). Any MCP-compatible client (Claude Desktop,
5
+ * Zed, Continue, etc.) can connect and call Skyloom agents as tools.
6
+ *
7
+ * Registered tools:
8
+ * - mcp_chat — single-turn question to any agent
9
+ * - mcp_task — multi-agent orchestration
10
+ * - list_agents — return available agent names + specialties
11
+ */
12
+
13
+ import * as readline from 'readline';
14
+ import { createSystemContext, orchestrateTask } from './factory';
15
+
16
+ const MCP_VERSION = '2025-03-26';
17
+ const SERVER_INFO = { name: 'skyloom', version: '1.4.0' };
18
+
19
+ const TOOL_DEFS = [
20
+ {
21
+ name: 'mcp_chat',
22
+ description: 'Ask a Skyloom agent a question. Use fair for companionship, fog for research, rain for code, frost for review, dew for ops, snow for planning.',
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ agent: { type: 'string', description: 'Agent name: fog/rain/frost/snow/dew/fair (default: fair)' },
27
+ message: { type: 'string', description: 'What to ask the agent' },
28
+ },
29
+ required: ['message'],
30
+ },
31
+ },
32
+ {
33
+ name: 'mcp_task',
34
+ description: 'Run multi-agent orchestration. Snow plans, then agents collaborate. Use for complex multi-step goals.',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: {
38
+ goal: { type: 'string', description: 'The goal to accomplish' },
39
+ },
40
+ required: ['goal'],
41
+ },
42
+ },
43
+ {
44
+ name: 'list_agents',
45
+ description: 'List available Skyloom agents and their specialties.',
46
+ inputSchema: { type: 'object', properties: {}, required: [] },
47
+ },
48
+ ];
49
+
50
+ export class MCPServer {
51
+ private initialized = false;
52
+ private ctx: any = null;
53
+ private agents: Map<string, any> = new Map();
54
+
55
+ async run(): Promise<void> {
56
+ const rl = readline.createInterface({ input: process.stdin });
57
+ for await (const line of rl) {
58
+ const trimmed = line.trim();
59
+ if (!trimmed) continue;
60
+ try {
61
+ const msg = JSON.parse(trimmed);
62
+ const resp = await this.handle(msg);
63
+ if (resp !== null) {
64
+ process.stdout.write(JSON.stringify(resp) + '\n');
65
+ }
66
+ } catch {
67
+ // Ignore parse errors
68
+ }
69
+ }
70
+ }
71
+
72
+ private async handle(msg: Record<string, any>): Promise<Record<string, any> | null> {
73
+ const mid = msg.id;
74
+ const method = msg.method || '';
75
+ const params = msg.params || {};
76
+
77
+ // Notifications — no response
78
+ if (method.startsWith('notifications/')) return null;
79
+
80
+ try {
81
+ let result: any;
82
+ if (method === 'initialize') {
83
+ result = await this.init();
84
+ } else if (method === 'tools/list') {
85
+ result = { tools: TOOL_DEFS };
86
+ } else if (method === 'tools/call') {
87
+ result = await this.toolsCall(params);
88
+ } else {
89
+ return { jsonrpc: '2.0', id: mid, error: { code: -32601, message: `unknown method: ${method}` } };
90
+ }
91
+ return { jsonrpc: '2.0', id: mid, result };
92
+ } catch (e: any) {
93
+ return { jsonrpc: '2.0', id: mid, error: { code: -32603, message: e.message || String(e) } };
94
+ }
95
+ }
96
+
97
+ private async init(): Promise<Record<string, any>> {
98
+ this.initialized = true;
99
+ return { protocolVersion: MCP_VERSION, capabilities: { tools: {} }, serverInfo: SERVER_INFO };
100
+ }
101
+
102
+ private async ensureCtx(): Promise<void> {
103
+ if (!this.ctx) {
104
+ this.ctx = createSystemContext();
105
+ await this.ctx.initAll();
106
+ this.agents = this.ctx.agentMap;
107
+ }
108
+ }
109
+
110
+ private async toolsCall(params: Record<string, any>): Promise<Record<string, any>> {
111
+ const name = params.name || '';
112
+ const args = params.arguments || {};
113
+
114
+ if (name === 'list_agents') return this.handleListAgents();
115
+ if (name === 'mcp_chat') return this.handleChat(args);
116
+ if (name === 'mcp_task') return this.handleTask(args);
117
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
118
+ }
119
+
120
+ private async handleListAgents(): Promise<Record<string, any>> {
121
+ await this.ensureCtx();
122
+ const lines = ['Available agents:'];
123
+ for (const [name, agent] of this.agents) {
124
+ lines.push(`- ${name} (${agent.displayName}): ${agent.specialty}`);
125
+ }
126
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
127
+ }
128
+
129
+ private async handleChat(args: Record<string, any>): Promise<Record<string, any>> {
130
+ const agentName = String(args.agent || 'fair').trim().toLowerCase();
131
+ const message = String(args.message || '').trim();
132
+ if (!message) {
133
+ return { content: [{ type: 'text', text: 'Error: message is required' }], isError: true };
134
+ }
135
+
136
+ await this.ensureCtx();
137
+ const agent = this.agents.get(agentName);
138
+ if (!agent) {
139
+ return { content: [{ type: 'text', text: `Unknown agent '${agentName}'. Available: ${[...this.agents.keys()].join(', ')}` }], isError: true };
140
+ }
141
+
142
+ try {
143
+ const reply = await agent.chat(message);
144
+ return { content: [{ type: 'text', text: reply }] };
145
+ } catch (e: any) {
146
+ return { content: [{ type: 'text', text: `Agent error: ${e.message || e}` }], isError: true };
147
+ }
148
+ }
149
+
150
+ private async handleTask(args: Record<string, any>): Promise<Record<string, any>> {
151
+ const goal = String(args.goal || '').trim();
152
+ if (!goal) {
153
+ return { content: [{ type: 'text', text: 'Error: goal is required' }], isError: true };
154
+ }
155
+
156
+ await this.ensureCtx();
157
+ try {
158
+ const [_tasks, results, summary] = await orchestrateTask(goal, this.agents);
159
+ const ok = results.filter(r => r.success).length;
160
+ const total = results.length;
161
+ const contentLines = [`[${ok}/${total} tasks completed]`];
162
+ for (const r of results) {
163
+ contentLines.push(`## ${r.agent}: ${r.description}\n${r.content || '(no content)'}`);
164
+ }
165
+ if (summary) contentLines.push(`\n${summary}`);
166
+ return { content: [{ type: 'text', text: contentLines.join('\n') }] };
167
+ } catch (e: any) {
168
+ return { content: [{ type: 'text', text: `Task error: ${e.message || e}` }], isError: true };
169
+ }
170
+ }
171
+ }
172
+
173
+ export async function startMCPServer(): Promise<void> {
174
+ const server = new MCPServer();
175
+ await server.run();
176
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * 模型配置管理 — unified default with optional per-agent overrides.
3
+ *
4
+ * Resolution order (already honored by LLMClient.getModel / getApiKey):
5
+ * model: agents.<name>.model → default_model
6
+ * apiKey: agents.<name>.api_key → env var → api_keys.<provider>
7
+ *
8
+ * This module provides the write path: mutate the *runtime* config object
9
+ * (shared by reference across LLMClient and every agent, so changes apply to
10
+ * the very next call — agents can hot-swap their own model mid-session) and
11
+ * persist a narrow patch to ~/.skyloom/config.yaml (never the merged config,
12
+ * so defaults don't leak into the user file).
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import * as yaml from 'yaml';
18
+ import { USER_CONFIG_DIR } from './config';
19
+ import { listProviders, modelsFor, validateModel } from './catalog';
20
+
21
+ export interface ModelDescription {
22
+ model: string;
23
+ /** 'agent' = per-agent override, 'unified' = default_model. */
24
+ source: 'agent' | 'unified';
25
+ provider: string | null;
26
+ /** Where the API key would come from for this agent. */
27
+ keySource: 'agent' | 'env' | 'global' | 'missing';
28
+ }
29
+
30
+ /** Find which provider a catalog model id belongs to. */
31
+ export function providerOfModel(modelId: string): string | null {
32
+ if (modelId.includes('/')) return modelId.split('/')[0];
33
+ for (const p of listProviders()) {
34
+ if (modelsFor(p).some(m => m.id === modelId)) return p;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /** Read-mutate-write the raw user config file (narrow patch). */
40
+ function patchUserConfig(mutate: (cfg: any) => void, dir: string = USER_CONFIG_DIR): void {
41
+ const file = path.join(dir, 'config.yaml');
42
+ let cfg: any = {};
43
+ if (fs.existsSync(file)) {
44
+ try { cfg = yaml.parse(fs.readFileSync(file, 'utf-8')) || {}; } catch { cfg = {}; }
45
+ }
46
+ mutate(cfg);
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ fs.writeFileSync(file, yaml.stringify(cfg), 'utf-8');
49
+ }
50
+
51
+ /** Apply the same mutation to the in-memory runtime config (hot effect). */
52
+ function ensureAgentSlot(runtimeConfig: any, agentName: string): any {
53
+ if (!runtimeConfig.agents) runtimeConfig.agents = {};
54
+ if (!runtimeConfig.agents[agentName]) runtimeConfig.agents[agentName] = {};
55
+ return runtimeConfig.agents[agentName];
56
+ }
57
+
58
+ export interface SetModelResult {
59
+ ok: boolean;
60
+ suggestions: string[];
61
+ provider: string | null;
62
+ }
63
+
64
+ /** Per-agent model override (独立配置). */
65
+ export function setAgentModel(
66
+ runtimeConfig: any,
67
+ agentName: string,
68
+ modelId: string,
69
+ dir?: string
70
+ ): SetModelResult {
71
+ const v = validateModel(modelId);
72
+ if (!v.ok) return { ok: false, suggestions: v.suggestions, provider: null };
73
+ const provider = providerOfModel(modelId);
74
+
75
+ const slot = ensureAgentSlot(runtimeConfig, agentName);
76
+ slot.model = modelId;
77
+ if (provider) slot.provider = provider;
78
+
79
+ patchUserConfig(cfg => {
80
+ if (!cfg.agents) cfg.agents = {};
81
+ if (!cfg.agents[agentName]) cfg.agents[agentName] = {};
82
+ cfg.agents[agentName].model = modelId;
83
+ if (provider) cfg.agents[agentName].provider = provider;
84
+ }, dir);
85
+ return { ok: true, suggestions: [], provider };
86
+ }
87
+
88
+ /** Remove the per-agent override — the agent follows the unified default again. */
89
+ export function clearAgentModel(runtimeConfig: any, agentName: string, dir?: string): void {
90
+ if (runtimeConfig.agents?.[agentName]) {
91
+ delete runtimeConfig.agents[agentName].model;
92
+ delete runtimeConfig.agents[agentName].provider;
93
+ }
94
+ patchUserConfig(cfg => {
95
+ if (cfg.agents?.[agentName]) {
96
+ delete cfg.agents[agentName].model;
97
+ delete cfg.agents[agentName].provider;
98
+ if (Object.keys(cfg.agents[agentName]).length === 0) delete cfg.agents[agentName];
99
+ }
100
+ }, dir);
101
+ }
102
+
103
+ /** 统一配置 — the default model every agent without an override uses. */
104
+ export function setUnifiedModel(runtimeConfig: any, modelId: string, dir?: string): SetModelResult {
105
+ const v = validateModel(modelId);
106
+ if (!v.ok) return { ok: false, suggestions: v.suggestions, provider: null };
107
+ const provider = providerOfModel(modelId);
108
+
109
+ runtimeConfig.default_model = modelId;
110
+ if (provider) runtimeConfig.default_provider = provider;
111
+
112
+ patchUserConfig(cfg => {
113
+ cfg.default_model = modelId;
114
+ if (provider) cfg.default_provider = provider;
115
+ }, dir);
116
+ return { ok: true, suggestions: [], provider };
117
+ }
118
+
119
+ /** Per-agent API key (独立 key;该 agent 的所有调用优先用它). */
120
+ export function setAgentApiKey(runtimeConfig: any, agentName: string, key: string, dir?: string): void {
121
+ ensureAgentSlot(runtimeConfig, agentName).api_key = key;
122
+ patchUserConfig(cfg => {
123
+ if (!cfg.agents) cfg.agents = {};
124
+ if (!cfg.agents[agentName]) cfg.agents[agentName] = {};
125
+ cfg.agents[agentName].api_key = key;
126
+ }, dir);
127
+ }
128
+
129
+ export function clearAgentApiKey(runtimeConfig: any, agentName: string, dir?: string): void {
130
+ if (runtimeConfig.agents?.[agentName]) delete runtimeConfig.agents[agentName].api_key;
131
+ patchUserConfig(cfg => {
132
+ if (cfg.agents?.[agentName]) delete cfg.agents[agentName].api_key;
133
+ }, dir);
134
+ }
135
+
136
+ /** Describe how an agent's model & key resolve right now. */
137
+ export function describeAgentLLM(runtimeConfig: any, agentName: string, dir: string = USER_CONFIG_DIR): ModelDescription {
138
+ const agentCfg = runtimeConfig.agents?.[agentName] || {};
139
+ const model: string = agentCfg.model
140
+ || runtimeConfig.default_model
141
+ || runtimeConfig.llm?.default_model
142
+ || 'gpt-4o';
143
+ const source: 'agent' | 'unified' = agentCfg.model ? 'agent' : 'unified';
144
+ const provider = providerOfModel(model);
145
+
146
+ let keySource: ModelDescription['keySource'] = 'missing';
147
+ if (agentCfg.api_key) keySource = 'agent';
148
+ else if (provider && process.env[`${provider.toUpperCase()}_API_KEY`]) keySource = 'env';
149
+ else {
150
+ try {
151
+ const file = path.join(dir, 'config.yaml');
152
+ const cfg = fs.existsSync(file) ? yaml.parse(fs.readFileSync(file, 'utf-8')) || {} : {};
153
+ if (provider && cfg.api_keys?.[provider]) keySource = 'global';
154
+ } catch { /* missing */ }
155
+ }
156
+ return { model, source, provider, keySource };
157
+ }