agentgui 1.0.453 → 1.0.455
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/.prd +428 -0
- package/database.js +119 -0
- package/lib/tool-manager.js +88 -0
- package/package.json +1 -1
- package/server.js +151 -0
- package/static/index.html +18 -0
- package/static/js/features.js +2 -0
- package/static/js/tool-status.js +48 -0
- package/test-acp-endpoints.js +0 -119
- package/test-wave4-ui.mjs +0 -141
- package/test-ws-optimization.js +0 -277
package/server.js
CHANGED
|
@@ -24,6 +24,7 @@ import { register as registerRunHandlers } from './lib/ws-handlers-run.js';
|
|
|
24
24
|
import { register as registerUtilHandlers } from './lib/ws-handlers-util.js';
|
|
25
25
|
import { startAll as startACPTools, stopAll as stopACPTools, getStatus as getACPStatus, getPort as getACPPort, queryModels as queryACPModels, touch as touchACP } from './lib/acp-manager.js';
|
|
26
26
|
import { installGMAgentConfigs } from './lib/gm-agent-configs.js';
|
|
27
|
+
import * as toolManager from './lib/tool-manager.js';
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
process.on('uncaughtException', (err, origin) => {
|
|
@@ -1780,6 +1781,141 @@ const server = http.createServer(async (req, res) => {
|
|
|
1780
1781
|
return;
|
|
1781
1782
|
}
|
|
1782
1783
|
|
|
1784
|
+
if (pathOnly === '/api/tools' && req.method === 'GET') {
|
|
1785
|
+
console.log('[TOOLS-API] Handling GET /api/tools');
|
|
1786
|
+
const tools = toolManager.getAllTools();
|
|
1787
|
+
const toolsWithUpdates = await Promise.all(tools.map(async (t) => {
|
|
1788
|
+
if (t.installed) {
|
|
1789
|
+
const updates = await toolManager.checkForUpdates(t.id, t.version);
|
|
1790
|
+
return { ...t, hasUpdate: updates.hasUpdate, latestVersion: updates.latestVersion };
|
|
1791
|
+
}
|
|
1792
|
+
return { ...t, hasUpdate: false, latestVersion: null };
|
|
1793
|
+
}));
|
|
1794
|
+
sendJSON(req, res, 200, { tools: toolsWithUpdates });
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/)) {
|
|
1799
|
+
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/)[1];
|
|
1800
|
+
const status = toolManager.checkToolStatus(toolId);
|
|
1801
|
+
if (!status) {
|
|
1802
|
+
sendJSON(req, res, 404, { error: 'Tool not found' });
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
if (status.installed) {
|
|
1806
|
+
const updates = await toolManager.checkForUpdates(toolId, status.version);
|
|
1807
|
+
status.hasUpdate = updates.hasUpdate;
|
|
1808
|
+
status.latestVersion = updates.latestVersion;
|
|
1809
|
+
}
|
|
1810
|
+
sendJSON(req, res, 200, status);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/) && req.method === 'POST') {
|
|
1815
|
+
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/)[1];
|
|
1816
|
+
const tool = toolManager.getToolConfig(toolId);
|
|
1817
|
+
if (!tool) {
|
|
1818
|
+
sendJSON(req, res, 404, { error: 'Tool not found' });
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
queries.updateToolStatus(toolId, { status: 'installing' });
|
|
1822
|
+
sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
|
|
1823
|
+
toolManager.install(toolId, (msg) => {
|
|
1824
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1825
|
+
wsOptimizer.broadcast({ type: 'tool_install_progress', toolId, data: msg });
|
|
1826
|
+
}
|
|
1827
|
+
}).then((result) => {
|
|
1828
|
+
if (result.success) {
|
|
1829
|
+
queries.updateToolStatus(toolId, { status: 'installed', version: result.version, installed_at: Date.now() });
|
|
1830
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1831
|
+
wsOptimizer.broadcast({ type: 'tool_install_complete', toolId, data: result });
|
|
1832
|
+
}
|
|
1833
|
+
queries.addToolInstallHistory(toolId, 'install', 'success', null);
|
|
1834
|
+
} else {
|
|
1835
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
1836
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1837
|
+
wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: result });
|
|
1838
|
+
}
|
|
1839
|
+
queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/) && req.method === 'POST') {
|
|
1846
|
+
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/)[1];
|
|
1847
|
+
const body = await parseBody(req);
|
|
1848
|
+
const tool = toolManager.getToolConfig(toolId);
|
|
1849
|
+
if (!tool) {
|
|
1850
|
+
sendJSON(req, res, 404, { error: 'Tool not found' });
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
const current = toolManager.checkToolStatus(toolId);
|
|
1854
|
+
if (!current || !current.installed) {
|
|
1855
|
+
sendJSON(req, res, 400, { error: 'Tool not installed' });
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
queries.updateToolStatus(toolId, { status: 'updating' });
|
|
1859
|
+
sendJSON(req, res, 200, { success: true, updating: true });
|
|
1860
|
+
toolManager.update(toolId, body.targetVersion, (msg) => {
|
|
1861
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1862
|
+
wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
|
|
1863
|
+
}
|
|
1864
|
+
}).then((result) => {
|
|
1865
|
+
if (result.success) {
|
|
1866
|
+
queries.updateToolStatus(toolId, { status: 'installed', version: result.version, installed_at: Date.now() });
|
|
1867
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1868
|
+
wsOptimizer.broadcast({ type: 'tool_update_complete', toolId, data: result });
|
|
1869
|
+
}
|
|
1870
|
+
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
1871
|
+
} else {
|
|
1872
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
1873
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1874
|
+
wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: result });
|
|
1875
|
+
}
|
|
1876
|
+
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/) && req.method === 'GET') {
|
|
1883
|
+
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/)[1];
|
|
1884
|
+
const url = new URL(req.url, 'http://localhost');
|
|
1885
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit')) || 20, 100);
|
|
1886
|
+
const offset = parseInt(url.searchParams.get('offset')) || 0;
|
|
1887
|
+
const history = queries.getToolInstallHistory(toolId, limit, offset);
|
|
1888
|
+
sendJSON(req, res, 200, { history });
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
if (pathOnly === '/api/tools/refresh-all' && req.method === 'POST') {
|
|
1893
|
+
sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
|
|
1894
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1895
|
+
wsOptimizer.broadcast({ type: 'tools_refresh_started' });
|
|
1896
|
+
}
|
|
1897
|
+
setImmediate(async () => {
|
|
1898
|
+
const tools = toolManager.getAllTools();
|
|
1899
|
+
for (const tool of tools) {
|
|
1900
|
+
queries.updateToolStatus(tool.id, {
|
|
1901
|
+
status: tool.installed ? 'installed' : 'not_installed',
|
|
1902
|
+
version: tool.version,
|
|
1903
|
+
last_check_at: Date.now()
|
|
1904
|
+
});
|
|
1905
|
+
if (tool.installed) {
|
|
1906
|
+
const updates = await toolManager.checkForUpdates(tool.id, tool.version);
|
|
1907
|
+
if (updates.hasUpdate) {
|
|
1908
|
+
queries.updateToolStatus(tool.id, { update_available: 1, latest_version: updates.latestVersion });
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1913
|
+
wsOptimizer.broadcast({ type: 'tools_refresh_complete', data: tools });
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1783
1919
|
if (pathOnly === '/api/ws-stats' && req.method === 'GET') {
|
|
1784
1920
|
const stats = wsOptimizer.getStats();
|
|
1785
1921
|
sendJSON(req, res, 200, stats);
|
|
@@ -4213,6 +4349,7 @@ function onServerReady() {
|
|
|
4213
4349
|
installGMAgentConfigs().catch(err => console.error('[GM-CONFIG] Startup error:', err.message));
|
|
4214
4350
|
|
|
4215
4351
|
startACPTools().then(() => {
|
|
4352
|
+
console.log('[ACP] On-demand startup enabled (ACP tools start when first used)');
|
|
4216
4353
|
setTimeout(() => {
|
|
4217
4354
|
const acpStatus = getACPStatus();
|
|
4218
4355
|
for (const s of acpStatus) {
|
|
@@ -4227,6 +4364,20 @@ function onServerReady() {
|
|
|
4227
4364
|
}, 6000);
|
|
4228
4365
|
}).catch(err => console.error('[ACP] Startup error:', err.message));
|
|
4229
4366
|
|
|
4367
|
+
const toolIds = ['gm-oc', 'gm-gc', 'gm-kilo', 'gm-cc'];
|
|
4368
|
+
queries.initializeToolInstallations(toolIds.map(id => ({ id })));
|
|
4369
|
+
for (const toolId of toolIds) {
|
|
4370
|
+
const status = toolManager.checkToolStatus(toolId);
|
|
4371
|
+
if (status) {
|
|
4372
|
+
queries.updateToolStatus(toolId, {
|
|
4373
|
+
status: status.installed ? 'installed' : 'not_installed',
|
|
4374
|
+
version: status.version,
|
|
4375
|
+
last_check_at: Date.now()
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
console.log('[TOOLS] Initialization complete');
|
|
4380
|
+
|
|
4230
4381
|
ensureModelsDownloaded().then(async ok => {
|
|
4231
4382
|
if (ok) console.log('[MODELS] Speech models ready');
|
|
4232
4383
|
else console.log('[MODELS] Speech model download failed');
|
package/static/index.html
CHANGED
|
@@ -1238,6 +1238,16 @@
|
|
|
1238
1238
|
100% { border-color: var(--color-border); }
|
|
1239
1239
|
}
|
|
1240
1240
|
|
|
1241
|
+
/* ===== TOOLS VIEW ===== */
|
|
1242
|
+
.tools-container {
|
|
1243
|
+
flex: 1;
|
|
1244
|
+
min-height: 0;
|
|
1245
|
+
overflow-y: auto;
|
|
1246
|
+
overflow-x: hidden;
|
|
1247
|
+
padding: 1.5rem 2rem;
|
|
1248
|
+
-webkit-overflow-scrolling: touch;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1241
1251
|
.voice-mic-btn.recording {
|
|
1242
1252
|
background: var(--color-error);
|
|
1243
1253
|
border-color: var(--color-error);
|
|
@@ -3025,6 +3035,7 @@
|
|
|
3025
3035
|
.toast-error { background: var(--color-error); color: white; }
|
|
3026
3036
|
.toast-warning { background: var(--color-warning); color: white; }
|
|
3027
3037
|
</style>
|
|
3038
|
+
<link rel="stylesheet" href="/gm/css/tool-status.css">
|
|
3028
3039
|
</head>
|
|
3029
3040
|
<body>
|
|
3030
3041
|
<!-- Sidebar overlay (mobile) -->
|
|
@@ -3111,6 +3122,7 @@
|
|
|
3111
3122
|
<button class="view-toggle-btn" data-view="files">Files</button>
|
|
3112
3123
|
<button class="view-toggle-btn" data-view="voice" id="voiceTabBtn" style="display:none;">Voice</button>
|
|
3113
3124
|
<button class="view-toggle-btn" data-view="terminal" id="terminalTabBtn">Terminal</button>
|
|
3125
|
+
<button class="view-toggle-btn" data-view="tools">Tools</button>
|
|
3114
3126
|
</div>
|
|
3115
3127
|
|
|
3116
3128
|
<!-- Messages scroll area -->
|
|
@@ -3174,6 +3186,11 @@
|
|
|
3174
3186
|
</div>
|
|
3175
3187
|
</div>
|
|
3176
3188
|
|
|
3189
|
+
<!-- Tools management view -->
|
|
3190
|
+
<div id="toolsContainer" class="tools-container" style="display:none;">
|
|
3191
|
+
<div id="tool-status-container"></div>
|
|
3192
|
+
</div>
|
|
3193
|
+
|
|
3177
3194
|
<!-- Input area: fixed at bottom -->
|
|
3178
3195
|
<div class="input-section">
|
|
3179
3196
|
<div class="input-wrapper">
|
|
@@ -3243,6 +3260,7 @@
|
|
|
3243
3260
|
<script defer src="/gm/js/conversations.js"></script>
|
|
3244
3261
|
<script defer src="/gm/js/terminal.js"></script>
|
|
3245
3262
|
<script defer src="/gm/js/script-runner.js"></script>
|
|
3263
|
+
<script defer src="/gm/js/tool-status.js"></script>
|
|
3246
3264
|
<script defer src="/gm/js/client.js"></script>
|
|
3247
3265
|
<script type="module" src="/gm/js/voice.js"></script>
|
|
3248
3266
|
<script defer src="/gm/js/features.js"></script>
|
package/static/js/features.js
CHANGED
|
@@ -149,6 +149,7 @@
|
|
|
149
149
|
var fileIframe = document.getElementById('fileBrowserIframe');
|
|
150
150
|
var voiceContainer = document.getElementById('voiceContainer');
|
|
151
151
|
var terminalContainer = document.getElementById('terminalContainer');
|
|
152
|
+
var toolsContainer = document.getElementById('toolsContainer');
|
|
152
153
|
if (!bar) return;
|
|
153
154
|
bar.querySelectorAll('.view-toggle-btn').forEach(function(btn) {
|
|
154
155
|
btn.classList.toggle('active', btn.dataset.view === view);
|
|
@@ -158,6 +159,7 @@
|
|
|
158
159
|
if (fileBrowser) fileBrowser.style.display = view === 'files' ? 'flex' : 'none';
|
|
159
160
|
if (voiceContainer) voiceContainer.style.display = view === 'voice' ? 'flex' : 'none';
|
|
160
161
|
if (terminalContainer) terminalContainer.style.display = view === 'terminal' ? 'flex' : 'none';
|
|
162
|
+
if (toolsContainer) toolsContainer.style.display = view === 'tools' ? 'flex' : 'none';
|
|
161
163
|
if (view === 'files' && fileIframe && currentConversation) {
|
|
162
164
|
var src = BASE + '/files/' + currentConversation + '/';
|
|
163
165
|
if (fileIframe.src !== location.origin + src) fileIframe.src = src;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const ToolStatusComponent = {
|
|
2
|
+
state: { tools: [], refreshing: false },
|
|
3
|
+
|
|
4
|
+
init() {
|
|
5
|
+
this.loadTools();
|
|
6
|
+
if (window.wsManager) {
|
|
7
|
+
const events = ['tool_install_started', 'tool_install_progress', 'tool_install_complete', 'tool_install_failed', 'tool_update_complete', 'tool_update_failed', 'tools_refresh_complete'];
|
|
8
|
+
events.forEach(e => window.wsManager.on(e, (d) => this.handleEvent(e, d)));
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async loadTools() {
|
|
13
|
+
try { const res = await fetch('/gm/api/tools'); this.state.tools = (await res.json()).tools || []; this.render(); } catch (e) { console.error('[TOOL-STATUS]', e.message); }
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async act(action, toolId) {
|
|
17
|
+
try { const res = await fetch(`/gm/api/tools/${toolId}/${action}`, { method: 'POST' }); const data = await res.json(); if (!data.success) alert(`${action} failed: ${data.error || 'Unknown error'}`); } catch (e) { alert(`${action} failed: ${e.message}`); }
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async refreshTools() { this.state.refreshing = true; this.render(); try { await fetch('/gm/api/tools/refresh-all', { method: 'POST' }); } catch (e) { console.error('[TOOL-STATUS]', e.message); } },
|
|
21
|
+
|
|
22
|
+
handleEvent(event, data) {
|
|
23
|
+
const tool = this.state.tools.find(t => t.id === data.toolId);
|
|
24
|
+
if (!tool) return;
|
|
25
|
+
if (event === 'tool_install_started') { tool.status = 'installing'; tool.progress = 0; }
|
|
26
|
+
else if (event === 'tool_install_progress') { tool.progress = Math.min((tool.progress || 0) + 5, 90); }
|
|
27
|
+
else if (event.includes('_complete')) { tool.status = 'installed'; tool.version = data.data.version; tool.hasUpdate = false; tool.progress = 100; setTimeout(() => this.loadTools(), 1000); }
|
|
28
|
+
else if (event.includes('_failed')) { tool.status = 'failed'; tool.error_message = data.data.error; tool.progress = 0; }
|
|
29
|
+
else if (event === 'tools_refresh_complete') { this.state.refreshing = false; this.loadTools(); return; }
|
|
30
|
+
this.render();
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
getStatusColor(tool) { return tool.status === 'installed' && !tool.hasUpdate ? '#4CAF50' : tool.status === 'installed' && tool.hasUpdate ? '#FFC107' : ['installing', 'updating'].includes(tool.status) ? '#2196F3' : tool.status === 'failed' ? '#F44336' : '#9E9E9E'; },
|
|
34
|
+
|
|
35
|
+
getStatusText(tool) { return tool.status === 'installed' ? (tool.hasUpdate ? `Update available (v${tool.latestVersion})` : `Installed v${tool.version || '?'}`) : tool.status === 'installing' ? 'Installing...' : tool.status === 'updating' ? 'Updating...' : tool.status === 'failed' ? 'Failed' : 'Not installed'; },
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
const c = document.getElementById('tool-status-container');
|
|
39
|
+
if (!c) return;
|
|
40
|
+
c.innerHTML = `
|
|
41
|
+
<div class="tool-status-header"><h3>Tools</h3><button onclick="window.toolStatus.refreshTools()" ${this.state.refreshing ? 'disabled' : ''} class="tool-refresh-btn">${this.state.refreshing ? 'Refreshing...' : 'Refresh'}</button></div>
|
|
42
|
+
<div class="tool-grid">${this.state.tools.map(t => `<div class="tool-card" style="border-left: 4px solid ${this.getStatusColor(t)}"><div class="tool-name">${t.name || t.id}</div><div class="tool-status">${this.getStatusText(t)}</div>${t.progress !== undefined && ['installing', 'updating'].includes(t.status) ? `<div class="tool-progress"><div class="progress-bar" style="width: ${t.progress}%"></div></div>` : ''}<div class="tool-actions">${!t.installed ? `<button onclick="window.toolStatus.act('install','${t.id}')" class="tool-btn tool-btn-primary">Install</button>` : t.hasUpdate ? `<button onclick="window.toolStatus.act('update','${t.id}')" class="tool-btn tool-btn-primary">Update</button>` : `<button onclick="window.toolStatus.refreshTools()" class="tool-btn tool-btn-secondary">Check Updates</button>`}${t.status === 'failed' ? `<button onclick="window.toolStatus.act('install','${t.id}')" class="tool-btn tool-btn-warning">Retry</button>` : ''}${t.error_message ? `<div class="tool-error" title="${t.error_message}">Error: ${t.error_message.substring(0, 30)}...<a href="#" onclick="alert('${t.error_message.replace(/"/g, '\\"')}'); return false;">Details</a></div>` : ''}</div></div>`).join('')}</div>
|
|
43
|
+
`;
|
|
44
|
+
window.toolStatus = this;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ToolStatusComponent.init()); } else { ToolStatusComponent.init(); }
|
package/test-acp-endpoints.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const http = require('http');
|
|
4
|
-
|
|
5
|
-
const BASE_URL = '/gm';
|
|
6
|
-
const PORT = 3000;
|
|
7
|
-
|
|
8
|
-
function makeRequest(method, path, body = null) {
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
const options = {
|
|
11
|
-
hostname: 'localhost',
|
|
12
|
-
port: PORT,
|
|
13
|
-
path: BASE_URL + path,
|
|
14
|
-
method: method,
|
|
15
|
-
headers: body ? {
|
|
16
|
-
'Content-Type': 'application/json',
|
|
17
|
-
'Content-Length': Buffer.byteLength(JSON.stringify(body))
|
|
18
|
-
} : {}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const req = http.request(options, (res) => {
|
|
22
|
-
let data = '';
|
|
23
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
24
|
-
res.on('end', () => {
|
|
25
|
-
try {
|
|
26
|
-
resolve({ status: res.statusCode, data: data ? JSON.parse(data) : null, raw: data });
|
|
27
|
-
} catch {
|
|
28
|
-
resolve({ status: res.statusCode, data: null, raw: data });
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
req.on('error', reject);
|
|
34
|
-
|
|
35
|
-
if (body) {
|
|
36
|
-
req.write(JSON.stringify(body));
|
|
37
|
-
}
|
|
38
|
-
req.end();
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function runTests() {
|
|
43
|
-
console.log('Testing ACP Agents & Stateless Runs Endpoints\n');
|
|
44
|
-
|
|
45
|
-
const tests = [
|
|
46
|
-
{
|
|
47
|
-
name: 'POST /api/agents/search - empty search',
|
|
48
|
-
test: async () => {
|
|
49
|
-
const res = await makeRequest('POST', '/api/agents/search', {});
|
|
50
|
-
return res.status === 200 && res.data.agents !== undefined;
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'POST /api/agents/search - search by name',
|
|
55
|
-
test: async () => {
|
|
56
|
-
const res = await makeRequest('POST', '/api/agents/search', { name: 'Claude' });
|
|
57
|
-
return res.status === 200 && Array.isArray(res.data.agents);
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'GET /api/agents/claude-code',
|
|
62
|
-
test: async () => {
|
|
63
|
-
const res = await makeRequest('GET', '/api/agents/claude-code');
|
|
64
|
-
return res.status === 200 || res.status === 404;
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'GET /api/agents/claude-code/descriptor',
|
|
69
|
-
test: async () => {
|
|
70
|
-
const res = await makeRequest('GET', '/api/agents/claude-code/descriptor');
|
|
71
|
-
return (res.status === 200 && res.data.metadata && res.data.specs) || res.status === 404;
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'POST /api/runs/search',
|
|
76
|
-
test: async () => {
|
|
77
|
-
const res = await makeRequest('POST', '/api/runs/search', {});
|
|
78
|
-
return res.status === 200 && res.data.runs !== undefined;
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'POST /api/runs - missing agent_id',
|
|
83
|
-
test: async () => {
|
|
84
|
-
const res = await makeRequest('POST', '/api/runs', {});
|
|
85
|
-
return res.status === 422;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
let passed = 0;
|
|
91
|
-
let failed = 0;
|
|
92
|
-
|
|
93
|
-
for (const t of tests) {
|
|
94
|
-
try {
|
|
95
|
-
const success = await t.test();
|
|
96
|
-
if (success) {
|
|
97
|
-
console.log(`✓ ${t.name}`);
|
|
98
|
-
passed++;
|
|
99
|
-
} else {
|
|
100
|
-
console.log(`✗ ${t.name}`);
|
|
101
|
-
failed++;
|
|
102
|
-
}
|
|
103
|
-
} catch (err) {
|
|
104
|
-
console.log(`✗ ${t.name} - ${err.message}`);
|
|
105
|
-
failed++;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
110
|
-
process.exit(failed > 0 ? 1 : 0);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
http.get(`http://localhost:${PORT}${BASE_URL}/`, (res) => {
|
|
114
|
-
console.log('Server is running\n');
|
|
115
|
-
runTests();
|
|
116
|
-
}).on('error', () => {
|
|
117
|
-
console.log('Server is not running. Please start with: npm run dev');
|
|
118
|
-
process.exit(1);
|
|
119
|
-
});
|
package/test-wave4-ui.mjs
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Wave 4 UI Consistency Test
|
|
4
|
-
* Tests agent/model persistence and display consolidation
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import http from 'http';
|
|
8
|
-
|
|
9
|
-
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
10
|
-
const API_BASE = `${BASE_URL}/gm/api`;
|
|
11
|
-
|
|
12
|
-
function request(method, path, body = null) {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
const url = new URL(path, API_BASE);
|
|
15
|
-
const options = {
|
|
16
|
-
method,
|
|
17
|
-
headers: body ? { 'Content-Type': 'application/json' } : {}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const req = http.request(url, options, (res) => {
|
|
21
|
-
let data = '';
|
|
22
|
-
res.on('data', chunk => data += chunk);
|
|
23
|
-
res.on('end', () => {
|
|
24
|
-
try {
|
|
25
|
-
resolve({ status: res.statusCode, data: data ? JSON.parse(data) : null });
|
|
26
|
-
} catch (e) {
|
|
27
|
-
resolve({ status: res.statusCode, data });
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
req.on('error', reject);
|
|
33
|
-
if (body) req.write(JSON.stringify(body));
|
|
34
|
-
req.end();
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function test() {
|
|
39
|
-
console.log('=== Wave 4 UI Consistency Tests ===\n');
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
// Test 1: Create conversation with specific agent and model
|
|
43
|
-
console.log('Test 1: Create conversation with agent and model');
|
|
44
|
-
const createRes = await request('POST', '/conversations', {
|
|
45
|
-
agentId: 'claude-code',
|
|
46
|
-
title: 'Wave 4 Test Conversation',
|
|
47
|
-
workingDirectory: '/tmp/test',
|
|
48
|
-
model: 'claude-sonnet-4-5'
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (createRes.status !== 200) {
|
|
52
|
-
console.error('❌ Failed to create conversation:', createRes.status);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const conversation = createRes.data.conversation;
|
|
57
|
-
console.log('✓ Created conversation:', conversation.id);
|
|
58
|
-
console.log(' - agentId:', conversation.agentId);
|
|
59
|
-
console.log(' - model:', conversation.model);
|
|
60
|
-
|
|
61
|
-
// Test 2: Fetch conversation and verify agent/model are returned
|
|
62
|
-
console.log('\nTest 2: Fetch conversation via /full endpoint');
|
|
63
|
-
const fullRes = await request('GET', `/conversations/${conversation.id}/full`);
|
|
64
|
-
|
|
65
|
-
if (fullRes.status !== 200) {
|
|
66
|
-
console.error('❌ Failed to fetch conversation:', fullRes.status);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const fullConv = fullRes.data.conversation;
|
|
71
|
-
console.log('✓ Fetched conversation');
|
|
72
|
-
console.log(' - agentId:', fullConv.agentId);
|
|
73
|
-
console.log(' - agentType:', fullConv.agentType);
|
|
74
|
-
console.log(' - model:', fullConv.model);
|
|
75
|
-
|
|
76
|
-
if (!fullConv.agentId && !fullConv.agentType) {
|
|
77
|
-
console.error('❌ agentId/agentType missing from response');
|
|
78
|
-
} else {
|
|
79
|
-
console.log('✓ agentId/agentType present');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!fullConv.model) {
|
|
83
|
-
console.error('❌ model missing from response');
|
|
84
|
-
} else {
|
|
85
|
-
console.log('✓ model present');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Test 3: List conversations and verify agent/model in list
|
|
89
|
-
console.log('\nTest 3: List conversations');
|
|
90
|
-
const listRes = await request('GET', '/conversations');
|
|
91
|
-
|
|
92
|
-
if (listRes.status !== 200) {
|
|
93
|
-
console.error('❌ Failed to list conversations:', listRes.status);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const listedConv = listRes.data.conversations.find(c => c.id === conversation.id);
|
|
98
|
-
if (!listedConv) {
|
|
99
|
-
console.error('❌ Conversation not found in list');
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
console.log('✓ Conversation in list');
|
|
104
|
-
console.log(' - agentId:', listedConv.agentId);
|
|
105
|
-
console.log(' - agentType:', listedConv.agentType);
|
|
106
|
-
console.log(' - model:', listedConv.model);
|
|
107
|
-
|
|
108
|
-
// Test 4: Update conversation model
|
|
109
|
-
console.log('\nTest 4: Update conversation model');
|
|
110
|
-
const updateRes = await request('POST', `/conversations/${conversation.id}`, {
|
|
111
|
-
model: 'claude-opus-4-6'
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
if (updateRes.status !== 200) {
|
|
115
|
-
console.error('❌ Failed to update conversation:', updateRes.status);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const updatedConv = updateRes.data.conversation;
|
|
120
|
-
console.log('✓ Updated conversation');
|
|
121
|
-
console.log(' - model:', updatedConv.model);
|
|
122
|
-
|
|
123
|
-
if (updatedConv.model !== 'claude-opus-4-6') {
|
|
124
|
-
console.error('❌ Model not updated correctly');
|
|
125
|
-
} else {
|
|
126
|
-
console.log('✓ Model updated correctly');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Cleanup
|
|
130
|
-
console.log('\nCleanup: Deleting test conversation');
|
|
131
|
-
await request('DELETE', `/conversations/${conversation.id}`);
|
|
132
|
-
console.log('✓ Deleted test conversation');
|
|
133
|
-
|
|
134
|
-
console.log('\n=== All Tests Passed ===');
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.error('❌ Test error:', error.message);
|
|
137
|
-
process.exit(1);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
test();
|