@bolloon/bolloon-agent 0.3.8 → 0.3.9
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/dist/agents/pi-sdk-tools.js +90 -9
- package/dist/constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js +19 -4
- package/dist/constraint-runtime/src/tools/PolymarketSDK/clobShared.js +104 -0
- package/dist/constraint-runtime/src/tools/PolymarketSDK/createOrder.js +34 -4
- package/dist/constraint-runtime/src/tools/PolymarketSDK/getOrders.js +17 -4
- package/dist/external-engines/delegate.js +148 -0
- package/dist/external-engines/discovery.js +394 -0
- package/dist/external-engines/index.js +9 -0
- package/dist/external-engines/types.js +11 -0
- package/dist/web/api-config.html +250 -1
- package/dist/web/routes-external-engines.js +111 -0
- package/dist/web/server.js +4 -0
- package/dist/web/style.css +31 -0
- package/package.json +1 -1
package/dist/web/api-config.html
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
<span class="api-tab-icon">🎵</span>
|
|
35
35
|
<span>音频生成</span>
|
|
36
36
|
</button>
|
|
37
|
+
<button class="api-tab" data-tab="engines" onclick="switchTab('engines')">
|
|
38
|
+
<span class="api-tab-icon">🤖</span>
|
|
39
|
+
<span>外部智能体</span>
|
|
40
|
+
</button>
|
|
37
41
|
</div>
|
|
38
42
|
|
|
39
43
|
<!-- LLM 面板 -->
|
|
@@ -63,6 +67,16 @@
|
|
|
63
67
|
</div>
|
|
64
68
|
</div>
|
|
65
69
|
|
|
70
|
+
<!-- 外部智能体面板 -->
|
|
71
|
+
<div class="api-panel" id="panel-engines" data-panel="engines" style="display: none;">
|
|
72
|
+
<div class="video-intro">
|
|
73
|
+
<p>Bolloon 自动发现本机已安装的 AI 编码工具(Codex / Claude Code / OpenCode / OpenClaw / Hermes)与实验目录声明的 API。可在此筛选模型并把它们的 API 当作 Bolloon 的供应商一键导入,无需重复填 Key。委派执行请通过对话里的 <code>delegate_to_engine</code> 工具。</p>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="provider-list" id="engineList">
|
|
76
|
+
<div class="loading-state">加载中...</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
66
80
|
<!-- 配置弹窗 -->
|
|
67
81
|
<div class="modal-overlay" id="configModal" style="display: none;">
|
|
68
82
|
<div class="modal-box">
|
|
@@ -186,6 +200,62 @@
|
|
|
186
200
|
</div>
|
|
187
201
|
</div>
|
|
188
202
|
|
|
203
|
+
<!-- 外部智能体 配置弹窗 -->
|
|
204
|
+
<div class="modal-overlay" id="engineModal" style="display: none;">
|
|
205
|
+
<div class="modal-box">
|
|
206
|
+
<div class="modal-header">
|
|
207
|
+
<div class="modal-title-group">
|
|
208
|
+
<div class="modal-icon" id="engineModalIcon">A</div>
|
|
209
|
+
<div>
|
|
210
|
+
<h2 id="engineModalTitle">配置</h2>
|
|
211
|
+
<p class="modal-subtitle" id="engineModalSubtitle">描述</p>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
<button class="modal-close" onclick="closeEngineModal()">×</button>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<form class="modal-form" id="engineConfigForm">
|
|
218
|
+
<div class="form-group">
|
|
219
|
+
<label>映射供应商</label>
|
|
220
|
+
<select id="engineProviderInput"></select>
|
|
221
|
+
<p class="form-hint">导入时写进该 Bolloon 供应商 (可覆盖发现的映射)</p>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="form-group">
|
|
225
|
+
<label>API Key</label>
|
|
226
|
+
<input type="password" id="engineApiKeyInput" placeholder="输入 API Key">
|
|
227
|
+
<p class="form-hint" id="engineApiKeyHint">已有 Key 会保留,输入新值以更新</p>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="form-group">
|
|
231
|
+
<label>Base URL</label>
|
|
232
|
+
<input type="text" id="engineBaseUrlInput" placeholder="https://api.example.com/v1">
|
|
233
|
+
<p class="form-hint">留空使用默认地址</p>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="form-group">
|
|
237
|
+
<label>模型(可筛选)</label>
|
|
238
|
+
<div class="combobox" id="engineModelCombobox">
|
|
239
|
+
<input type="text" id="engineModelInput" placeholder="输入关键字筛选模型…" autocomplete="off">
|
|
240
|
+
<div class="combobox-list" id="engineModelList" style="display: none;"></div>
|
|
241
|
+
</div>
|
|
242
|
+
<p class="form-hint" id="engineModelHint">从发现到的候选模型里筛选,或手动输入自定义模型名</p>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div class="form-group">
|
|
246
|
+
<button type="button" class="test-btn" id="engineImportBtn" onclick="importEngine()">
|
|
247
|
+
⚡ 导入为供应商
|
|
248
|
+
</button>
|
|
249
|
+
<div class="test-result" id="engineImportResult" style="display: none;"></div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="form-actions">
|
|
253
|
+
<button type="button" class="btn-cancel" onclick="closeEngineModal()">取消</button>
|
|
254
|
+
</div>
|
|
255
|
+
</form>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
189
259
|
<script>
|
|
190
260
|
// 当前 tab
|
|
191
261
|
let currentTab = 'llm';
|
|
@@ -194,7 +264,9 @@
|
|
|
194
264
|
let llmConfigData = null;
|
|
195
265
|
let videoConfigData = null;
|
|
196
266
|
let audioConfigData = null;
|
|
267
|
+
let engineData = null;
|
|
197
268
|
let currentProvider = null;
|
|
269
|
+
let currentEngine = null;
|
|
198
270
|
let currentProviderType = 'llm'; // 'llm' | 'video' | 'audio'
|
|
199
271
|
|
|
200
272
|
// ==================== Tab 切换 ====================
|
|
@@ -211,6 +283,13 @@
|
|
|
211
283
|
}
|
|
212
284
|
|
|
213
285
|
function updateCountBadge() {
|
|
286
|
+
if (currentTab === 'engines') {
|
|
287
|
+
const list = engineData && engineData.engines ? engineData.engines : [];
|
|
288
|
+
const total = list.length;
|
|
289
|
+
const available = list.filter(e => e.available).length;
|
|
290
|
+
document.getElementById('configCount').textContent = available + '/' + total + ' 智能体可用';
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
214
293
|
const data = currentTab === 'llm' ? llmConfigData :
|
|
215
294
|
currentTab === 'video' ? videoConfigData : audioConfigData;
|
|
216
295
|
if (!data) {
|
|
@@ -235,7 +314,19 @@
|
|
|
235
314
|
|
|
236
315
|
// ==================== 加载 ====================
|
|
237
316
|
async function loadAll() {
|
|
238
|
-
await Promise.all([loadLLMConfig(), loadVideoConfig(), loadAudioConfig()]);
|
|
317
|
+
await Promise.all([loadLLMConfig(), loadVideoConfig(), loadAudioConfig(), loadEngines()]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function loadEngines() {
|
|
321
|
+
try {
|
|
322
|
+
const resp = await fetch('/api/external-engines');
|
|
323
|
+
engineData = await resp.json();
|
|
324
|
+
renderEngines();
|
|
325
|
+
if (currentTab === 'engines') updateCountBadge();
|
|
326
|
+
} catch (err) {
|
|
327
|
+
const el = document.getElementById('engineList');
|
|
328
|
+
if (el) el.innerHTML = '<div class="error-state">加载失败: ' + err.message + '</div>';
|
|
329
|
+
}
|
|
239
330
|
}
|
|
240
331
|
|
|
241
332
|
async function loadLLMConfig() {
|
|
@@ -518,6 +609,164 @@
|
|
|
518
609
|
}
|
|
519
610
|
};
|
|
520
611
|
|
|
612
|
+
// ==================== 外部智能体 渲染 ====================
|
|
613
|
+
function renderEngines() {
|
|
614
|
+
const listEl = document.getElementById('engineList');
|
|
615
|
+
if (!listEl) return;
|
|
616
|
+
const list = engineData && engineData.engines ? engineData.engines : [];
|
|
617
|
+
if (list.length === 0) {
|
|
618
|
+
listEl.innerHTML = '<div class="error-state">未发现本机已安装的外部编码智能体,也未找到实验目录 API(BOLLOON_EXPERIMENT_API_DIR)</div>';
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
let html = '';
|
|
622
|
+
for (const e of list) {
|
|
623
|
+
const isExperiment = String(e.id).startsWith('experiment:');
|
|
624
|
+
let statusText, statusClass;
|
|
625
|
+
if (e.available) { statusText = '可用'; statusClass = 'status-green'; }
|
|
626
|
+
else if (e.installed) { statusText = '已装未配'; statusClass = 'status-yellow'; }
|
|
627
|
+
else if (e.configured) { statusText = '已配未装'; statusClass = 'status-yellow'; }
|
|
628
|
+
else { statusText = '未发现'; statusClass = 'status-muted'; }
|
|
629
|
+
|
|
630
|
+
html += `
|
|
631
|
+
<div class="provider-card ${e.available ? 'active' : ''}" onclick="openEngineModal('${e.id}')">
|
|
632
|
+
<div class="provider-header">
|
|
633
|
+
<div class="provider-icon">${isExperiment ? '🧪' : e.displayName.charAt(0).toUpperCase()}</div>
|
|
634
|
+
<div class="provider-info">
|
|
635
|
+
<h3 class="provider-name">${e.displayName}</h3>
|
|
636
|
+
<p class="provider-desc">${e.id}${e.provider ? ' → ' + e.provider : ''}${e.cliPath ? ' · 已安装' : ''}</p>
|
|
637
|
+
</div>
|
|
638
|
+
<div class="status-badge ${statusClass}">
|
|
639
|
+
<span class="status-dot"></span>
|
|
640
|
+
${statusText}
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
<div class="provider-footer">
|
|
644
|
+
<div class="config-status">
|
|
645
|
+
<span class="status-icon ${e.configured ? 'configured' : ''}">${e.configured ? '✓' : '○'}</span>
|
|
646
|
+
${e.configured ? '已配置 API' : '未配置 API'}
|
|
647
|
+
</div>
|
|
648
|
+
${e.model ? '<div class="model-info">模型: ' + e.model + '</div>' : ''}
|
|
649
|
+
${e.models && e.models.length ? '<div class="model-info">候选模型: ' + e.models.length + ' 个(可筛选)</div>' : ''}
|
|
650
|
+
<div class="arrow-indicator">›</div>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
`;
|
|
654
|
+
}
|
|
655
|
+
listEl.innerHTML = html;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// ==================== 外部智能体 配置弹窗 ====================
|
|
659
|
+
const ENGINE_PROVIDER_FALLBACK = ['openai','anthropic','ollama','openrouter','gemini','minimax','deepseek','kimi','glm','qwen','mimo','local'];
|
|
660
|
+
|
|
661
|
+
function openEngineModal(id) {
|
|
662
|
+
const list = engineData && engineData.engines ? engineData.engines : [];
|
|
663
|
+
const e = list.find(x => x.id === id);
|
|
664
|
+
if (!e) return;
|
|
665
|
+
currentEngine = e;
|
|
666
|
+
|
|
667
|
+
document.getElementById('engineModalIcon').textContent = String(e.displayName).charAt(0).toUpperCase();
|
|
668
|
+
document.getElementById('engineModalTitle').textContent = '配置 ' + e.displayName;
|
|
669
|
+
document.getElementById('engineModalSubtitle').textContent = (e.notes || '') + (e.configPath ? ' · 配置: ' + e.configPath : '');
|
|
670
|
+
|
|
671
|
+
// 映射供应商 select
|
|
672
|
+
const provSel = document.getElementById('engineProviderInput');
|
|
673
|
+
const provKeys = (llmConfigData && llmConfigData.providers) ? Object.keys(llmConfigData.providers) : ENGINE_PROVIDER_FALLBACK;
|
|
674
|
+
const mapped = e.provider || 'openai';
|
|
675
|
+
provSel.innerHTML = provKeys.map(k => `<option value="${k}" ${k === mapped ? 'selected' : ''}>${k}</option>`).join('');
|
|
676
|
+
|
|
677
|
+
document.getElementById('engineApiKeyInput').value = '';
|
|
678
|
+
document.getElementById('engineApiKeyHint').textContent = e.apiKey
|
|
679
|
+
? '当前已发现 (***' + (e.apiKey.slice(-4) || '') + '),输入新值以更新'
|
|
680
|
+
: '输入 API Key';
|
|
681
|
+
document.getElementById('engineBaseUrlInput').value = e.baseUrl || '';
|
|
682
|
+
document.getElementById('engineModelInput').value = e.model || '';
|
|
683
|
+
document.getElementById('engineModelHint').textContent = (e.models && e.models.length)
|
|
684
|
+
? '从 ' + e.models.length + ' 个候选模型里筛选,或手动输入'
|
|
685
|
+
: '手动输入模型名';
|
|
686
|
+
|
|
687
|
+
document.getElementById('engineImportResult').style.display = 'none';
|
|
688
|
+
document.getElementById('engineModal').style.display = 'flex';
|
|
689
|
+
// 准备模型筛选下拉
|
|
690
|
+
renderEngineModelList('');
|
|
691
|
+
setTimeout(() => document.getElementById('engineModelInput').focus(), 50);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function closeEngineModal() {
|
|
695
|
+
document.getElementById('engineModal').style.display = 'none';
|
|
696
|
+
currentEngine = null;
|
|
697
|
+
document.getElementById('engineModelList').style.display = 'none';
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// 模型筛选下拉
|
|
701
|
+
function renderEngineModelList(filter) {
|
|
702
|
+
const listEl = document.getElementById('engineModelList');
|
|
703
|
+
if (!currentEngine || !currentEngine.models) { listEl.style.display = 'none'; return; }
|
|
704
|
+
const f = (filter || '').toLowerCase();
|
|
705
|
+
const matched = currentEngine.models.filter(m => m.toLowerCase().includes(f));
|
|
706
|
+
if (matched.length === 0) { listEl.style.display = 'none'; return; }
|
|
707
|
+
listEl.innerHTML = matched.map(m => `<div class="combobox-option" onclick="selectEngineModel('${m.replace(/'/g, "\\'")}')">${m}</div>`).join('');
|
|
708
|
+
listEl.style.display = 'block';
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function selectEngineModel(model) {
|
|
712
|
+
document.getElementById('engineModelInput').value = model;
|
|
713
|
+
document.getElementById('engineModelList').style.display = 'none';
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
document.getElementById('engineModelInput').addEventListener('input', (ev) => {
|
|
717
|
+
renderEngineModelList(ev.target.value);
|
|
718
|
+
});
|
|
719
|
+
document.getElementById('engineModelInput').addEventListener('focus', (ev) => {
|
|
720
|
+
renderEngineModelList(ev.target.value);
|
|
721
|
+
});
|
|
722
|
+
// 点击空白关闭下拉
|
|
723
|
+
document.addEventListener('click', (ev) => {
|
|
724
|
+
const cb = document.getElementById('engineModelCombobox');
|
|
725
|
+
if (cb && !cb.contains(ev.target)) {
|
|
726
|
+
document.getElementById('engineModelList').style.display = 'none';
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
document.getElementById('engineModal').addEventListener('click', (ev) => {
|
|
730
|
+
if (ev.target === document.getElementById('engineModal')) closeEngineModal();
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
async function importEngine() {
|
|
734
|
+
if (!currentEngine) return;
|
|
735
|
+
const btn = document.getElementById('engineImportBtn');
|
|
736
|
+
const result = document.getElementById('engineImportResult');
|
|
737
|
+
btn.disabled = true;
|
|
738
|
+
requestAnimationFrame(() => { btn.textContent = '⚡ 导入中...'; });
|
|
739
|
+
result.style.display = 'none';
|
|
740
|
+
|
|
741
|
+
const body = {
|
|
742
|
+
id: currentEngine.id,
|
|
743
|
+
model: document.getElementById('engineModelInput').value.trim(),
|
|
744
|
+
provider: document.getElementById('engineProviderInput').value,
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
try {
|
|
748
|
+
const resp = await fetch('/api/external-engines/import', {
|
|
749
|
+
method: 'POST',
|
|
750
|
+
headers: { 'Content-Type': 'application/json' },
|
|
751
|
+
body: JSON.stringify(body),
|
|
752
|
+
});
|
|
753
|
+
const data = await resp.json();
|
|
754
|
+
if (!resp.ok) throw new Error(data.error || '导入失败');
|
|
755
|
+
result.className = 'test-result test-success';
|
|
756
|
+
result.innerHTML = '<span>✓</span> 已导入为供应商: ' + (data.provider || '') +
|
|
757
|
+
(data.autoActivated ? '(已激活)' : '') + ',可在「LLM 对话」tab 选用';
|
|
758
|
+
result.style.display = 'flex';
|
|
759
|
+
// 刷新 LLM 配置 + 引擎列表
|
|
760
|
+
await Promise.all([loadLLMConfig(), loadEngines()]);
|
|
761
|
+
} catch (err) {
|
|
762
|
+
result.className = 'test-result test-error';
|
|
763
|
+
result.innerHTML = '<span>✗</span> ' + (err.message || '导入失败');
|
|
764
|
+
result.style.display = 'flex';
|
|
765
|
+
}
|
|
766
|
+
btn.disabled = false;
|
|
767
|
+
requestAnimationFrame(() => { btn.textContent = '⚡ 导入为供应商'; });
|
|
768
|
+
}
|
|
769
|
+
|
|
521
770
|
// ==================== 关闭弹窗 ====================
|
|
522
771
|
document.getElementById('configModal').onclick = function(e) {
|
|
523
772
|
if (e.target === this) closeModal();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routes-external-engines.ts — 外部编码智能体 配置/委派 路由
|
|
3
|
+
*
|
|
4
|
+
* 三个能力:
|
|
5
|
+
* GET /api/external-engines 发现本机已装的引擎 (脱敏)
|
|
6
|
+
* POST /api/external-engines/import 把发现的引擎 API 写进 Bolloon provider 体系 (当供应商)
|
|
7
|
+
* POST /api/external-engines/run 委派编码任务给引擎 CLI (子智能体)
|
|
8
|
+
*
|
|
9
|
+
* 复用 routes-llm-config.ts 的 llmConfigStore + initMinimax 做激活.
|
|
10
|
+
*/
|
|
11
|
+
import { discoverEngines, mapEngineToProviderConfig, resolveProvider } from '../external-engines/index.js';
|
|
12
|
+
import { llmConfigStore } from '../llm/config-store.js';
|
|
13
|
+
import { initMinimax } from '../constraints/index.js';
|
|
14
|
+
function maskKey(key) {
|
|
15
|
+
if (!key)
|
|
16
|
+
return '';
|
|
17
|
+
if (key.length <= 4)
|
|
18
|
+
return '***';
|
|
19
|
+
return '***' + key.slice(-4);
|
|
20
|
+
}
|
|
21
|
+
export function registerExternalEngineRoutes(app) {
|
|
22
|
+
// ==================== 发现 ====================
|
|
23
|
+
app.get('/api/external-engines', async (_req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const engines = await discoverEngines();
|
|
26
|
+
const safe = engines.map((e) => ({
|
|
27
|
+
...e,
|
|
28
|
+
apiKey: maskKey(e.apiKey),
|
|
29
|
+
}));
|
|
30
|
+
res.json({ engines: safe });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// ==================== 导入为供应商 ====================
|
|
37
|
+
app.post('/api/external-engines/import', async (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const { id, model, provider: providerOverride } = req.body || {};
|
|
40
|
+
if (!id)
|
|
41
|
+
return res.status(400).json({ error: 'id 必填' });
|
|
42
|
+
const engines = await discoverEngines();
|
|
43
|
+
const engine = engines.find((e) => e.id === id);
|
|
44
|
+
if (!engine)
|
|
45
|
+
return res.status(404).json({ error: `未发现的引擎: ${id}` });
|
|
46
|
+
if (!engine.configured) {
|
|
47
|
+
return res.status(400).json({ error: `引擎 ${id} 未配置 API key / baseUrl, 无法导入为供应商` });
|
|
48
|
+
}
|
|
49
|
+
// 允许前端在导入时覆盖 model / provider (API 配置 UI 里用户筛选模型后传来)
|
|
50
|
+
if (model && typeof model === 'string' && model.trim()) {
|
|
51
|
+
engine.model = model.trim();
|
|
52
|
+
}
|
|
53
|
+
if (providerOverride && typeof providerOverride === 'string' && providerOverride.trim()) {
|
|
54
|
+
const resolved = resolveProvider(providerOverride.trim(), engine.provider || 'openai');
|
|
55
|
+
engine.provider = resolved;
|
|
56
|
+
}
|
|
57
|
+
let importPatch;
|
|
58
|
+
try {
|
|
59
|
+
importPatch = mapEngineToProviderConfig(engine);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return res.status(400).json({ error: e.message });
|
|
63
|
+
}
|
|
64
|
+
const { provider, patch } = importPatch;
|
|
65
|
+
await llmConfigStore.updateProvider(provider, patch);
|
|
66
|
+
// 导入即激活 (若提供了有效 key, 或不需要 key 的 provider)
|
|
67
|
+
const shouldActivate = !!patch.apiKey || !engine.apiKey;
|
|
68
|
+
let autoActivated = false;
|
|
69
|
+
if (shouldActivate) {
|
|
70
|
+
try {
|
|
71
|
+
await llmConfigStore.setActiveProvider(provider);
|
|
72
|
+
initMinimax({
|
|
73
|
+
provider: provider,
|
|
74
|
+
apiKey: patch.apiKey || undefined,
|
|
75
|
+
baseUrl: patch.baseUrl || undefined,
|
|
76
|
+
model: patch.model || undefined,
|
|
77
|
+
});
|
|
78
|
+
autoActivated = true;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// 激活失败不阻断导入 (可能该 provider 仍需别的字段)
|
|
82
|
+
console.warn('[external-engines] 激活失败:', e?.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
res.json({ ok: true, provider, autoActivated, imported: { ...patch, apiKey: maskKey(patch.apiKey) } });
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
// ==================== 委派执行 ====================
|
|
92
|
+
app.post('/api/external-engines/run', async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { id, prompt, cwd, model } = req.body || {};
|
|
95
|
+
if (!id)
|
|
96
|
+
return res.status(400).json({ error: 'id 必填' });
|
|
97
|
+
if (!prompt)
|
|
98
|
+
return res.status(400).json({ error: 'prompt 必填' });
|
|
99
|
+
// 动态 import 避免顶层循环依赖 (delegate -> discovery, 这里反向)
|
|
100
|
+
const { delegateToEngine } = await import('../external-engines/delegate.js');
|
|
101
|
+
const result = await delegateToEngine(id, prompt, {
|
|
102
|
+
cwd,
|
|
103
|
+
...(model ? { model: String(model) } : {}),
|
|
104
|
+
});
|
|
105
|
+
res.json(result);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
package/dist/web/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { segmentChatReply } from '../agents/chat-segmenter.js';
|
|
|
12
12
|
import { listTools } from '../llm/tool-manifest/index.js';
|
|
13
13
|
import { registerJudgmentsRoutes } from './routes-judgments.js';
|
|
14
14
|
import { registerLlmConfigRoutes } from './routes-llm-config.js';
|
|
15
|
+
import { registerExternalEngineRoutes } from './routes-external-engines.js';
|
|
15
16
|
import { registerTaskRoutes } from './routes-tasks.js';
|
|
16
17
|
import { registerHearthRoutes } from './routes-hearth.js';
|
|
17
18
|
// 2026-07-06: 类型抽到 ./server-types.ts (channel / session / task / sse client / iroh info / paths)
|
|
@@ -3929,6 +3930,9 @@ ${goalDesc}
|
|
|
3929
3930
|
registerTaskRoutes(app, { broadcast, getAgentForChannel });
|
|
3930
3931
|
// 2026-07-06: LLM/Video/Audio 配置路由抽到 ./routes-llm-config.ts
|
|
3931
3932
|
registerLlmConfigRoutes(app);
|
|
3933
|
+
// 2026-07-22: 外部编码智能体 (codex/claude-code/opencode/openclaw/hermes/实验 API)
|
|
3934
|
+
// 发现 + 配置为供应商 + 委派
|
|
3935
|
+
registerExternalEngineRoutes(app);
|
|
3932
3936
|
// ==================== P2P Network API ====================
|
|
3933
3937
|
// 获取当前身份
|
|
3934
3938
|
app.get('/api/identity', async (_req, res) => {
|
package/dist/web/style.css
CHANGED
|
@@ -2820,6 +2820,37 @@ body {
|
|
|
2820
2820
|
margin-top: 6px;
|
|
2821
2821
|
}
|
|
2822
2822
|
|
|
2823
|
+
/* 模型筛选下拉 (外部智能体配置弹窗) */
|
|
2824
|
+
.combobox {
|
|
2825
|
+
position: relative;
|
|
2826
|
+
}
|
|
2827
|
+
.combobox-list {
|
|
2828
|
+
position: absolute;
|
|
2829
|
+
top: calc(100% + 4px);
|
|
2830
|
+
left: 0;
|
|
2831
|
+
right: 0;
|
|
2832
|
+
max-height: 240px;
|
|
2833
|
+
overflow-y: auto;
|
|
2834
|
+
background: var(--bg-elevated, var(--bg));
|
|
2835
|
+
border: 1px solid var(--border);
|
|
2836
|
+
border-radius: var(--radius-sm);
|
|
2837
|
+
z-index: 50;
|
|
2838
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
2839
|
+
}
|
|
2840
|
+
.combobox-option {
|
|
2841
|
+
padding: 9px 14px;
|
|
2842
|
+
font-size: 13px;
|
|
2843
|
+
color: var(--text);
|
|
2844
|
+
cursor: pointer;
|
|
2845
|
+
white-space: nowrap;
|
|
2846
|
+
overflow: hidden;
|
|
2847
|
+
text-overflow: ellipsis;
|
|
2848
|
+
}
|
|
2849
|
+
.combobox-option:hover {
|
|
2850
|
+
background: var(--accent);
|
|
2851
|
+
color: #fff;
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2823
2854
|
.form-row {
|
|
2824
2855
|
display: flex;
|
|
2825
2856
|
gap: 12px;
|