jarvis-agent-factory 3.2.0 → 3.3.0
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/package.json +1 -1
- package/src/engine/agents.html +197 -0
- package/src/engine/server.js +73 -2
package/package.json
CHANGED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang=zh>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1">
|
|
5
|
+
<title>Agents · Jarvis Engine</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root{--bg:#0d1117;--card:#161b22;--border:#30363d;--text:#c9d1d9;--muted:#8b949e;--accent:#FF6B35;--green:#3fb950;--blue:#58a6ff;--purple:#bc8cff;--yellow:#d2991d}
|
|
8
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
9
|
+
body{font-family:system-ui;background:var(--bg);color:var(--text);min-height:100vh}
|
|
10
|
+
header{background:var(--card);border-bottom:1px solid var(--border);padding:12px 24px;display:flex;justify-content:space-between;align-items:center}
|
|
11
|
+
h1{color:var(--accent);font-size:18px}
|
|
12
|
+
nav{display:flex;gap:8px}
|
|
13
|
+
nav a{color:var(--muted);text-decoration:none;padding:6px 14px;border-radius:6px;font-size:13px;transition:all .2s}
|
|
14
|
+
nav a:hover,nav a.active{color:var(--text);background:var(--border)}
|
|
15
|
+
main{max-width:1200px;margin:0 auto;padding:24px}
|
|
16
|
+
.toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;flex-wrap:wrap;gap:12px}
|
|
17
|
+
.toolbar .info{color:var(--muted);font-size:13px}
|
|
18
|
+
.model-filter{display:flex;gap:6px;flex-wrap:wrap}
|
|
19
|
+
.model-filter button{padding:4px 10px;border-radius:12px;border:1px solid var(--border);background:var(--card);color:var(--muted);cursor:pointer;font-size:11px;transition:all .2s}
|
|
20
|
+
.model-filter button:hover,.model-filter button.sel{border-color:var(--accent);color:var(--accent)}
|
|
21
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px}
|
|
22
|
+
/* Pixel card */
|
|
23
|
+
.pixel-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:16px;cursor:pointer;transition:all .2s;position:relative;overflow:hidden}
|
|
24
|
+
.pixel-card:hover{border-color:var(--accent);transform:translateY(-2px)}
|
|
25
|
+
.pixel-card.custom{border-color:var(--purple)}
|
|
26
|
+
.pixel-card .role-tag{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
|
|
27
|
+
.pixel-card .agent-name{font-size:14px;font-weight:600;margin:4px 0}
|
|
28
|
+
.pixel-card .model-name{font-size:11px;color:var(--accent);margin-top:4px}
|
|
29
|
+
.pixel-card .model-name.custom{color:var(--purple)}
|
|
30
|
+
/* Pixel art avatar — 12x12 CSS grid */
|
|
31
|
+
.pixel-avatar{width:60px;height:60px;margin:8px auto;image-rendering:pixelated;position:relative}
|
|
32
|
+
.pixel-avatar .px{position:absolute;width:5px;height:5px;border-radius:0}
|
|
33
|
+
/* Pixel art patterns per icon */
|
|
34
|
+
.px-brain{background:var(--purple)} .px-layout{background:var(--blue)} .px-palette{background:var(--accent)}
|
|
35
|
+
.px-database{background:var(--green)} .px-test{background:var(--yellow)} .px-server{background:var(--blue)}
|
|
36
|
+
.px-route{background:var(--accent)} .px-cog{background:var(--muted)} .px-table{background:var(--green)}
|
|
37
|
+
.px-globe{background:var(--blue)} .px-play{background:var(--green)} .px-file{background:var(--muted)}
|
|
38
|
+
.px-map{background:var(--purple)} .px-list{background:var(--yellow)} .px-shield{background:var(--accent)}
|
|
39
|
+
.px-eye{background:var(--purple)}
|
|
40
|
+
/* Pixel patterns — simplified 8x8 */
|
|
41
|
+
.avatar-8{display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(8,1fr);width:56px;height:56px;gap:1px;margin:8px auto}
|
|
42
|
+
.avatar-8 span{width:100%;height:100%;border-radius:1px}
|
|
43
|
+
/* Model selector popup */
|
|
44
|
+
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:99;justify-content:center;align-items:center}
|
|
45
|
+
.modal-overlay.show{display:flex}
|
|
46
|
+
.modal{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:24px;min-width:320px;max-width:400px}
|
|
47
|
+
.modal h3{font-size:16px;margin-bottom:4px}
|
|
48
|
+
.modal .sub{font-size:12px;color:var(--muted);margin-bottom:16px}
|
|
49
|
+
.modal select{width:100%;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:8px;font-size:13px;margin-bottom:12px}
|
|
50
|
+
.modal .btns{display:flex;gap:8px;justify-content:flex-end}
|
|
51
|
+
.modal .btn{padding:8px 18px;border-radius:8px;border:1px solid var(--border);background:var(--bg);color:var(--text);cursor:pointer;font-size:13px}
|
|
52
|
+
.modal .btn.save{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
53
|
+
.modal .btn.reset{color:var(--muted)}
|
|
54
|
+
.toast{position:fixed;top:16px;right:16px;background:var(--green);color:#000;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:600;z-index:999;animation:fadein .3s}
|
|
55
|
+
@keyframes fadein{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}
|
|
56
|
+
.empty{text-align:center;padding:40px;color:var(--muted)}
|
|
57
|
+
</style>
|
|
58
|
+
</head>
|
|
59
|
+
<body>
|
|
60
|
+
<header>
|
|
61
|
+
<div style=display:flex;gap:16px;align-items:center>
|
|
62
|
+
<h1>🧠 Jarvis Engine</h1>
|
|
63
|
+
<nav><a href=/dashboard>Pipeline</a><a href=/agents class=active>Agents</a></nav>
|
|
64
|
+
</div>
|
|
65
|
+
<span style=font-size:12px;color:var(--muted) id=agentCount>17 agents</span>
|
|
66
|
+
</header>
|
|
67
|
+
<main>
|
|
68
|
+
<div class=toolbar>
|
|
69
|
+
<div class=info id=toolbarInfo>Click a card to configure model</div>
|
|
70
|
+
<div class=model-filter id=modelFilter></div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class=grid id=agentsGrid><div class=empty>Loading...</div></div>
|
|
73
|
+
</main>
|
|
74
|
+
<div class=modal-overlay id=modal>
|
|
75
|
+
<div class=modal>
|
|
76
|
+
<h3 id=modalName>---</h3>
|
|
77
|
+
<div class=sub id=modalRole>---</div>
|
|
78
|
+
<select id=modalSelect></select>
|
|
79
|
+
<div class=btns>
|
|
80
|
+
<button class="btn reset" onclick=resetModel()>↩ Reset default</button>
|
|
81
|
+
<button class="btn save" onclick=saveModel()>💾 Save</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div id=toastContainer></div>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
let agents=[],currentAgent=null,currentFilter='all';
|
|
89
|
+
|
|
90
|
+
const PIXEL_PATTERNS = {
|
|
91
|
+
brain: [0,0,0,1,1,0,0,0, 0,1,1,1,1,1,1,0, 1,1,0,1,1,0,1,1, 1,1,0,1,1,0,1,1, 1,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,0, 0,0,1,0,0,1,0,0, 0,0,0,1,1,0,0,0],
|
|
92
|
+
layout:[0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,1, 1,0,1,1,1,0,0,1, 1,0,1,1,1,0,0,1, 1,0,0,0,0,0,0,1, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0],
|
|
93
|
+
palette:[0,0,1,0,0,1,0,0, 0,1,1,1,1,1,1,0, 1,0,0,0,0,0,0,1, 1,0,1,0,0,1,0,1, 1,0,0,1,1,0,0,1, 1,0,0,0,0,0,0,1, 0,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0],
|
|
94
|
+
database:[0,0,1,1,1,1,0,0, 0,1,0,0,0,0,1,0, 1,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,0,0,0,0,1, 1,1,1,1,1,1,1,1],
|
|
95
|
+
test:[0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 1,1,0,0,0,0,1,1, 0,0,1,0,0,1,0,0, 0,0,0,1,1,0,0,0, 0,0,1,0,0,1,0,0, 1,1,0,0,0,0,1,1, 0,0,0,0,0,0,0,0],
|
|
96
|
+
server:[0,0,1,1,1,1,0,0, 0,1,0,0,0,0,1,0, 1,0,1,1,1,0,0,1, 1,0,1,0,1,0,0,1, 1,0,1,1,1,0,0,1, 1,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,1, 0,1,1,1,1,1,1,0],
|
|
97
|
+
route:[0,0,0,0,0,0,1,0, 0,0,0,0,0,1,0,0, 0,0,0,0,1,0,1,0, 0,0,0,1,0,0,1,0, 0,0,1,0,0,0,1,0, 0,1,0,0,0,0,1,0, 1,0,0,0,0,0,1,0, 0,0,0,0,0,0,1,0],
|
|
98
|
+
cog:[0,0,0,1,1,0,0,0, 0,0,1,0,0,1,0,0, 0,1,0,1,1,0,1,0, 1,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,1, 0,1,0,1,1,0,1,0, 0,0,1,0,0,1,0,0, 0,0,0,1,1,0,0,0],
|
|
99
|
+
table:[1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,1, 1,0,0,0,0,0,0,1, 1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,1, 1,1,1,1,1,1,1,1],
|
|
100
|
+
globe:[0,0,1,1,1,1,0,0, 0,1,0,1,1,0,1,0, 1,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,0,0,0,0,1, 1,0,1,0,0,1,0,1, 0,1,0,0,0,0,1,0, 0,0,1,1,1,1,0,0],
|
|
101
|
+
play:[0,0,0,0,0,0,0,0, 0,1,1,0,0,0,0,0, 0,1,1,1,0,0,0,0, 0,1,1,1,1,0,0,0, 0,1,1,1,1,1,0,0, 0,1,1,1,0,0,0,0, 0,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,0],
|
|
102
|
+
file:[1,1,1,1,1,0,0,0, 1,0,0,0,1,0,0,0, 1,0,0,0,1,0,0,0, 1,0,0,0,1,0,0,0, 1,0,0,0,1,0,0,0, 1,0,0,0,1,0,0,0, 1,1,1,1,1,0,0,0, 1,1,1,1,1,1,1,0],
|
|
103
|
+
map:[0,0,0,1,1,0,0,0, 0,0,1,0,0,1,0,0, 0,1,0,1,1,0,1,0, 1,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,1, 0,1,0,0,0,0,1,0, 0,0,1,0,0,1,0,0, 0,0,0,1,1,0,0,0],
|
|
104
|
+
list:[1,1,1,1,1,0,0,0, 1,0,0,0,0,0,0,0, 1,0,0,0,0,0,0,0, 0,1,1,1,1,0,0,0, 0,0,0,0,0,1,0,0, 0,0,0,0,0,1,0,0, 0,0,0,0,0,1,0,0, 1,1,1,1,1,1,0,0],
|
|
105
|
+
shield:[0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,0, 1,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,0,0,0,0,1, 0,1,0,0,0,0,1,0, 0,0,1,1,1,1,0,0],
|
|
106
|
+
eye:[0,0,0,0,0,0,0,0, 0,1,1,1,0,1,1,0, 1,0,0,1,0,0,0,1, 1,0,0,1,0,0,0,1, 0,1,1,1,0,1,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const ICON_COLORS = {
|
|
110
|
+
brain:'#bc8cff', layout:'#58a6ff', palette:'#FF6B35', database:'#3fb950', test:'#d2991d',
|
|
111
|
+
server:'#58a6ff', route:'#FF6B35', cog:'#8b949e', table:'#3fb950', globe:'#58a6ff',
|
|
112
|
+
play:'#3fb950', file:'#8b949e', map:'#bc8cff', list:'#d2991d', shield:'#FF6B35', eye:'#bc8cff',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function pixelAvatar(icon) {
|
|
116
|
+
const pattern = PIXEL_PATTERNS[icon] || PIXEL_PATTERNS.brain;
|
|
117
|
+
const color = ICON_COLORS[icon] || ICON_COLORS.brain;
|
|
118
|
+
return `<div class=avatar-8>${pattern.map(v => `<span style="background:${v?color:'transparent'}"></span>`).join('')}</div>`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function load() {
|
|
122
|
+
try {
|
|
123
|
+
const r = await fetch('/api/agents');
|
|
124
|
+
const data = await r.json();
|
|
125
|
+
agents = data.agents;
|
|
126
|
+
renderAgents();
|
|
127
|
+
// Model filter buttons
|
|
128
|
+
const models = [...new Set(agents.map(a=>a.model))];
|
|
129
|
+
document.getElementById('modelFilter').innerHTML = [
|
|
130
|
+
`<button class="${currentFilter==='all'?'sel':''}" onclick="filterBy('all')">All</button>`,
|
|
131
|
+
...models.map(m => `<button class="${currentFilter===m?'sel':''}" onclick="filterBy('${m}')">${m}</button>`)
|
|
132
|
+
].join('');
|
|
133
|
+
} catch(e) { console.error(e); }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderAgents() {
|
|
137
|
+
const filtered = currentFilter === 'all' ? agents : agents.filter(a => a.model === currentFilter);
|
|
138
|
+
document.getElementById('agentCount').textContent = filtered.length + ' agents';
|
|
139
|
+
document.getElementById('agentsGrid').innerHTML = filtered.map(a =>
|
|
140
|
+
`<div class="pixel-card${a.is_custom?' custom':''}" onclick="openModal('${a.id}')">
|
|
141
|
+
${pixelAvatar(a.icon)}
|
|
142
|
+
<div class=role-tag>${a.role}</div>
|
|
143
|
+
<div class=agent-name>${a.name}</div>
|
|
144
|
+
<div class="model-name${a.is_custom?' custom':''}">${a.model}</div>
|
|
145
|
+
</div>`
|
|
146
|
+
).join('');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function filterBy(model) { currentFilter = model; renderAgents(); load(); }
|
|
150
|
+
|
|
151
|
+
function openModal(id) {
|
|
152
|
+
currentAgent = agents.find(a => a.id === id);
|
|
153
|
+
if (!currentAgent) return;
|
|
154
|
+
document.getElementById('modalName').textContent = currentAgent.name;
|
|
155
|
+
document.getElementById('modalRole').textContent = currentAgent.role + ' · Default: ' + currentAgent.defaultModel;
|
|
156
|
+
const sel = document.getElementById('modalSelect');
|
|
157
|
+
sel.innerHTML = data?.available_models?.map(m => `<option value="${m}" ${m===currentAgent.model?'selected':''}>${m}</option>`).join('') || '';
|
|
158
|
+
document.getElementById('modal').classList.add('show');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function loadModels() {
|
|
162
|
+
const r = await fetch('/api/agents'); data = await r.json();
|
|
163
|
+
agents = data.agents;
|
|
164
|
+
const sel = document.getElementById('modalSelect');
|
|
165
|
+
sel.innerHTML = data.available_models.map(m => `<option value="${m}">${m}</option>`).join('');
|
|
166
|
+
renderAgents();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let data = null;
|
|
170
|
+
|
|
171
|
+
async function saveModel() {
|
|
172
|
+
const model = document.getElementById('modalSelect').value;
|
|
173
|
+
await fetch('/api/agents', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({agent_id:currentAgent.id, model}) });
|
|
174
|
+
document.getElementById('modal').classList.remove('show');
|
|
175
|
+
toast(`💾 ${currentAgent.name} → ${model}`);
|
|
176
|
+
loadModels();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function resetModel() {
|
|
180
|
+
await fetch('/api/agents', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({agent_id:currentAgent.id, model:currentAgent.defaultModel}) });
|
|
181
|
+
document.getElementById('modal').classList.remove('show');
|
|
182
|
+
toast(`↩ ${currentAgent.name} → default (${currentAgent.defaultModel})`);
|
|
183
|
+
loadModels();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
document.getElementById('modal').addEventListener('click', e => { if (e.target === e.currentTarget) document.getElementById('modal').classList.remove('show'); });
|
|
187
|
+
|
|
188
|
+
function toast(msg) {
|
|
189
|
+
const t = document.createElement('div'); t.className='toast'; t.textContent=msg;
|
|
190
|
+
document.getElementById('toastContainer').appendChild(t);
|
|
191
|
+
setTimeout(() => t.remove(), 2500);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
loadModels();
|
|
195
|
+
</script>
|
|
196
|
+
</body>
|
|
197
|
+
</html>
|
package/src/engine/server.js
CHANGED
|
@@ -366,6 +366,77 @@ export async function startEngine({ port = DEFAULT_PORT, dashboard = false, proj
|
|
|
366
366
|
// ---- Health ----
|
|
367
367
|
app.get('/health', (_req, res) => res.json({ status: 'ok', version: readPkgVersion(), tools: ['pipeline_init', 'pipeline_status', 'gate_enforce', 'advance_gate', 'report_status'] }));
|
|
368
368
|
|
|
369
|
+
// ---- Agent Model Config ----
|
|
370
|
+
const agentConfigPath = join(root, '.jarvis', 'agent-models.json');
|
|
371
|
+
|
|
372
|
+
function readAgentConfig() {
|
|
373
|
+
if (!existsSync(agentConfigPath)) return {};
|
|
374
|
+
try { return JSON.parse(readFileSync(agentConfigPath, 'utf-8')); } catch { return {}; }
|
|
375
|
+
}
|
|
376
|
+
function writeAgentConfig(cfg) {
|
|
377
|
+
const dir = join(root, '.jarvis'); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
378
|
+
writeFileSync(agentConfigPath, JSON.stringify(cfg, null, 2));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const AVAILABLE_MODELS = [
|
|
382
|
+
'deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek/deepseek-v4-pro', 'deepseek/deepseek-v4-flash',
|
|
383
|
+
'gpt-5.5', 'gpt-5.4', 'gpt-5.3-codex', 'gpt-5.3-codex-spark', 'gpt-5.4-mini', 'gpt-5.2',
|
|
384
|
+
'claude-opus-4-7', 'claude-sonnet-4-6', 'claude-haiku-4-5',
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
const AGENT_LIST = [
|
|
388
|
+
{ id:'jarvis', name:'Jarvis', role:'编排中枢', icon:'brain', defaultModel:'deepseek-v4-pro' },
|
|
389
|
+
{ id:'frontend-implementer', name:'Frontend', role:'前端全栈', icon:'layout', defaultModel:'deepseek-v4-pro' },
|
|
390
|
+
{ id:'frontend-ui-worker', name:'UI Worker', role:'UI/样式', icon:'palette', defaultModel:'deepseek-v4-flash' },
|
|
391
|
+
{ id:'frontend-state-worker', name:'State Worker', role:'状态/数据', icon:'database', defaultModel:'deepseek-v4-flash' },
|
|
392
|
+
{ id:'frontend-test-worker', name:'Frontend Test', role:'前端测试', icon:'test', defaultModel:'deepseek-v4-flash' },
|
|
393
|
+
{ id:'backend-implementer', name:'Backend', role:'后端全栈', icon:'server', defaultModel:'deepseek-v4-pro' },
|
|
394
|
+
{ id:'backend-api-worker', name:'API Worker', role:'API/路由', icon:'route', defaultModel:'deepseek-v4-flash' },
|
|
395
|
+
{ id:'backend-service-worker', name:'Service Worker', role:'业务逻辑', icon:'cog', defaultModel:'deepseek-v4-flash' },
|
|
396
|
+
{ id:'backend-data-worker', name:'Data Worker', role:'数据层', icon:'table', defaultModel:'deepseek-v4-flash' },
|
|
397
|
+
{ id:'backend-test-worker', name:'Backend Test', role:'后端测试', icon:'test', defaultModel:'deepseek-v4-flash' },
|
|
398
|
+
{ id:'browser-test-worker', name:'Browser Test', role:'浏览器测试', icon:'globe', defaultModel:'deepseek-v4-flash' },
|
|
399
|
+
{ id:'e2e-test-worker', name:'E2E Test', role:'端到端测试', icon:'play', defaultModel:'deepseek-v4-flash' },
|
|
400
|
+
{ id:'api-docs-worker', name:'API Docs', role:'API文档', icon:'file', defaultModel:'deepseek-v4-flash' },
|
|
401
|
+
{ id:'planner', name:'Planner', role:'执行规划', icon:'map', defaultModel:'deepseek-v4-pro' },
|
|
402
|
+
{ id:'task-design', name:'Task Design', role:'任务分解', icon:'list', defaultModel:'deepseek-v4-pro' },
|
|
403
|
+
{ id:'security-auditor', name:'Security', role:'安全审计', icon:'shield', defaultModel:'deepseek-v4-pro' },
|
|
404
|
+
{ id:'review-qa', name:'Review QA', role:'评审', icon:'eye', defaultModel:'deepseek-v4-pro' },
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
// REST: agent config
|
|
408
|
+
app.get('/api/agents', (_req, res) => {
|
|
409
|
+
const cfg = readAgentConfig();
|
|
410
|
+
const list = AGENT_LIST.map(a => ({ ...a, model: cfg[a.id] || a.defaultModel }));
|
|
411
|
+
res.json({ agents: list, available_models: AVAILABLE_MODELS });
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
app.post('/api/agents', (req, res) => {
|
|
415
|
+
const { agent_id, model } = req.body;
|
|
416
|
+
if (!agent_id || !model) return res.status(400).json({ error: 'agent_id and model required' });
|
|
417
|
+
if (!AVAILABLE_MODELS.includes(model)) return res.status(400).json({ error: `Unknown model. Available: ${AVAILABLE_MODELS.join(', ')}` });
|
|
418
|
+
const cfg = readAgentConfig();
|
|
419
|
+
cfg[agent_id] = model;
|
|
420
|
+
writeAgentConfig(cfg);
|
|
421
|
+
res.json({ ok: true, agent_id, model });
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// MCP: agent_config
|
|
425
|
+
server.tool('agent_config', '配置子 Agent 模型。读取/设置特定 Agent 的模型。', {
|
|
426
|
+
agent_id: z.string().optional().describe('Agent ID(不传则列出全部)'),
|
|
427
|
+
model: z.string().optional().describe('模型名(不传则只读当前配置)'),
|
|
428
|
+
}, async ({ agent_id, model }) => {
|
|
429
|
+
const cfg = readAgentConfig();
|
|
430
|
+
if (agent_id && model) {
|
|
431
|
+
if (!AVAILABLE_MODELS.includes(model)) return { content: [{ type: 'text', text: JSON.stringify({ error: `Unknown model. Available: ${AVAILABLE_MODELS.join(', ')}` }) }] };
|
|
432
|
+
cfg[agent_id] = model;
|
|
433
|
+
writeAgentConfig(cfg);
|
|
434
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, agent_id, model, message: `${agent_id} → ${model}` }) }] };
|
|
435
|
+
}
|
|
436
|
+
const list = AGENT_LIST.map(a => ({ id: a.id, name: a.name, role: a.role, model: cfg[a.id] || a.defaultModel, is_custom: !!cfg[a.id] }));
|
|
437
|
+
return { content: [{ type: 'text', text: JSON.stringify({ agents: list, available_models: AVAILABLE_MODELS }) }] };
|
|
438
|
+
});
|
|
439
|
+
|
|
369
440
|
// ---- SSE (real-time pipeline events) ----
|
|
370
441
|
const sseClients = new Set();
|
|
371
442
|
app.get('/api/events', (req, res) => {
|
|
@@ -385,8 +456,8 @@ export async function startEngine({ port = DEFAULT_PORT, dashboard = false, proj
|
|
|
385
456
|
|
|
386
457
|
// ---- Dashboard ----
|
|
387
458
|
if (dashboard) {
|
|
388
|
-
|
|
389
|
-
app.get('/
|
|
459
|
+
app.get('/dashboard', (_req, res) => res.type('html').send(readFileSync(resolve(import.meta.dirname, 'dashboard.html'), 'utf-8')));
|
|
460
|
+
app.get('/agents', (_req, res) => res.type('html').send(readFileSync(resolve(import.meta.dirname, 'agents.html'), 'utf-8')));
|
|
390
461
|
}
|
|
391
462
|
|
|
392
463
|
app.listen(port, () => {
|