agentgui 1.0.413 → 1.0.415
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/lib/ws-handlers-session.js +14 -1
- package/package.json +1 -1
- package/server.js +163 -20
- package/static/index.html +41 -30
- package/static/js/client.js +210 -96
- package/static/js/terminal.js +132 -103
|
@@ -97,7 +97,20 @@ export function register(router, deps) {
|
|
|
97
97
|
return data;
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
router.handle('agent.ls', () =>
|
|
100
|
+
router.handle('agent.ls', () => {
|
|
101
|
+
// Get local agents only (avoid external HTTP calls in WebSocket to prevent recursion)
|
|
102
|
+
const localAgents = discoveredAgents.map(agent => ({
|
|
103
|
+
id: agent.id,
|
|
104
|
+
name: agent.name,
|
|
105
|
+
icon: agent.icon,
|
|
106
|
+
path: agent.path,
|
|
107
|
+
protocol: agent.protocol || 'unknown',
|
|
108
|
+
description: agent.description || '',
|
|
109
|
+
status: 'available'
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
return { agents: localAgents };
|
|
113
|
+
});
|
|
101
114
|
|
|
102
115
|
router.handle('agent.get', (p) => {
|
|
103
116
|
const a = discoveredAgents.find(x => x.id === p.id);
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -342,31 +342,96 @@ function findCommand(cmd) {
|
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
async function queryACPServerAgents(baseUrl) {
|
|
346
|
+
try {
|
|
347
|
+
// Ensure correct endpoint format (handle both /api and /api/ cases)
|
|
348
|
+
const endpoint = baseUrl.endsWith('/') ? `${baseUrl}agents/search` : `${baseUrl}/agents/search`;
|
|
349
|
+
const response = await fetch(endpoint, {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: {
|
|
352
|
+
'Content-Type': 'application/json',
|
|
353
|
+
},
|
|
354
|
+
body: JSON.stringify({}),
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
console.error(`Failed to query ACP agents from ${baseUrl}: ${response.status}`);
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const data = await response.json();
|
|
363
|
+
if (!data.agents || !Array.isArray(data.agents)) {
|
|
364
|
+
console.error(`Invalid agents response from ${baseUrl}`);
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Convert ACP agent format to our internal format
|
|
369
|
+
return data.agents.map(agent => ({
|
|
370
|
+
id: agent.agent_id || agent.id,
|
|
371
|
+
name: agent.metadata?.ref?.name || agent.name || 'Unknown Agent',
|
|
372
|
+
icon: agent.metadata?.ref?.name?.charAt(0) || 'A',
|
|
373
|
+
path: baseUrl,
|
|
374
|
+
protocol: 'acp',
|
|
375
|
+
description: agent.metadata?.description || '',
|
|
376
|
+
}));
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error(`Error querying ACP server ${baseUrl}:`, error.message);
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
345
383
|
function discoverAgents() {
|
|
346
384
|
const agents = [];
|
|
347
385
|
const binaries = [
|
|
348
|
-
{ cmd: 'claude', id: 'claude-code', name: 'Claude Code', icon: 'C' },
|
|
349
|
-
{ cmd: 'opencode', id: 'opencode', name: 'OpenCode', icon: 'O' },
|
|
350
|
-
{ cmd: 'gemini', id: 'gemini', name: 'Gemini CLI', icon: 'G' },
|
|
351
|
-
{ cmd: 'kilo', id: 'kilo', name: 'Kilo Code', icon: 'K' },
|
|
352
|
-
{ cmd: 'goose', id: 'goose', name: 'Goose', icon: 'g' },
|
|
353
|
-
{ cmd: 'openhands', id: 'openhands', name: 'OpenHands', icon: 'H' },
|
|
354
|
-
{ cmd: 'augment', id: 'augment', name: 'Augment Code', icon: 'A' },
|
|
355
|
-
{ cmd: 'cline', id: 'cline', name: 'Cline', icon: 'c' },
|
|
356
|
-
{ cmd: 'kimi', id: 'kimi', name: 'Kimi CLI', icon: 'K' },
|
|
357
|
-
{ cmd: 'qwen-code', id: 'qwen', name: 'Qwen Code', icon: 'Q' },
|
|
358
|
-
{ cmd: 'codex', id: 'codex', name: 'Codex CLI', icon: 'X' },
|
|
359
|
-
{ cmd: 'mistral-vibe', id: 'mistral', name: 'Mistral Vibe', icon: 'M' },
|
|
360
|
-
{ cmd: 'kiro', id: 'kiro', name: 'Kiro CLI', icon: 'k' },
|
|
361
|
-
{ cmd: 'fast-agent', id: 'fast-agent', name: 'fast-agent', icon: 'F' },
|
|
386
|
+
{ cmd: 'claude', id: 'claude-code', name: 'Claude Code', icon: 'C', protocol: 'cli' },
|
|
387
|
+
{ cmd: 'opencode', id: 'opencode', name: 'OpenCode', icon: 'O', protocol: 'acp' },
|
|
388
|
+
{ cmd: 'gemini', id: 'gemini', name: 'Gemini CLI', icon: 'G', protocol: 'acp' },
|
|
389
|
+
{ cmd: 'kilo', id: 'kilo', name: 'Kilo Code', icon: 'K', protocol: 'acp' },
|
|
390
|
+
{ cmd: 'goose', id: 'goose', name: 'Goose', icon: 'g', protocol: 'acp' },
|
|
391
|
+
{ cmd: 'openhands', id: 'openhands', name: 'OpenHands', icon: 'H', protocol: 'acp' },
|
|
392
|
+
{ cmd: 'augment', id: 'augment', name: 'Augment Code', icon: 'A', protocol: 'acp' },
|
|
393
|
+
{ cmd: 'cline', id: 'cline', name: 'Cline', icon: 'c', protocol: 'acp' },
|
|
394
|
+
{ cmd: 'kimi', id: 'kimi', name: 'Kimi CLI', icon: 'K', protocol: 'acp' },
|
|
395
|
+
{ cmd: 'qwen-code', id: 'qwen', name: 'Qwen Code', icon: 'Q', protocol: 'acp' },
|
|
396
|
+
{ cmd: 'codex', id: 'codex', name: 'Codex CLI', icon: 'X', protocol: 'acp' },
|
|
397
|
+
{ cmd: 'mistral-vibe', id: 'mistral', name: 'Mistral Vibe', icon: 'M', protocol: 'acp' },
|
|
398
|
+
{ cmd: 'kiro', id: 'kiro', name: 'Kiro CLI', icon: 'k', protocol: 'acp' },
|
|
399
|
+
{ cmd: 'fast-agent', id: 'fast-agent', name: 'fast-agent', icon: 'F', protocol: 'acp' },
|
|
362
400
|
];
|
|
363
401
|
for (const bin of binaries) {
|
|
364
402
|
const result = findCommand(bin.cmd);
|
|
365
|
-
if (result) agents.push({
|
|
403
|
+
if (result) agents.push({
|
|
404
|
+
id: bin.id,
|
|
405
|
+
name: bin.name,
|
|
406
|
+
icon: bin.icon,
|
|
407
|
+
path: result,
|
|
408
|
+
protocol: bin.protocol
|
|
409
|
+
});
|
|
366
410
|
}
|
|
367
411
|
return agents;
|
|
368
412
|
}
|
|
369
413
|
|
|
414
|
+
// Function to discover agents from external ACP servers
|
|
415
|
+
async function discoverExternalACPServers() {
|
|
416
|
+
// Default ACP servers to query (excluding local server to prevent recursion)
|
|
417
|
+
const acpServers = [
|
|
418
|
+
'http://localhost:8080', // Common default ACP port
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
const externalAgents = [];
|
|
422
|
+
for (const serverUrl of acpServers) {
|
|
423
|
+
try {
|
|
424
|
+
console.log(`Querying ACP agents from: ${serverUrl}`);
|
|
425
|
+
const agents = await queryACPServerAgents(serverUrl);
|
|
426
|
+
externalAgents.push(...agents);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error(`Failed to query ${serverUrl}:`, error.message);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return externalAgents;
|
|
433
|
+
}
|
|
434
|
+
|
|
370
435
|
const discoveredAgents = discoverAgents();
|
|
371
436
|
initializeDescriptors(discoveredAgents);
|
|
372
437
|
|
|
@@ -569,6 +634,11 @@ async function getModelsForAgent(agentId) {
|
|
|
569
634
|
|
|
570
635
|
// Fallback default models for agents that fail to retrieve models
|
|
571
636
|
const fallbackModels = {
|
|
637
|
+
'claude-code': [
|
|
638
|
+
{ id: 'haiku', label: 'Haiku (Default)' },
|
|
639
|
+
{ id: 'sonnet', label: 'Sonnet' },
|
|
640
|
+
{ id: 'opus', label: 'Opus' }
|
|
641
|
+
],
|
|
572
642
|
'gemini': [
|
|
573
643
|
{ id: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro' },
|
|
574
644
|
{ id: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash' },
|
|
@@ -585,15 +655,65 @@ async function getModelsForAgent(agentId) {
|
|
|
585
655
|
{ id: 'claude-sonnet-4', label: 'Claude Sonnet 4' },
|
|
586
656
|
{ id: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' },
|
|
587
657
|
{ id: 'gpt-4', label: 'GPT-4' }
|
|
658
|
+
],
|
|
659
|
+
'goose': [
|
|
660
|
+
{ id: 'default', label: 'Default Model' },
|
|
661
|
+
{ id: 'goose-pro', label: 'Goose Pro' }
|
|
662
|
+
],
|
|
663
|
+
'openhands': [
|
|
664
|
+
{ id: 'default', label: 'Default Model' },
|
|
665
|
+
{ id: 'openhands-1', label: 'OpenHands 1' }
|
|
666
|
+
],
|
|
667
|
+
'augment': [
|
|
668
|
+
{ id: 'default', label: 'Default Model' },
|
|
669
|
+
{ id: 'augment-pro', label: 'Augment Pro' }
|
|
670
|
+
],
|
|
671
|
+
'cline': [
|
|
672
|
+
{ id: 'default', label: 'Default Model' },
|
|
673
|
+
{ id: 'cline-1', label: 'Cline 1' }
|
|
674
|
+
],
|
|
675
|
+
'kimi': [
|
|
676
|
+
{ id: 'default', label: 'Default Model' },
|
|
677
|
+
{ id: 'kimi-pro', label: 'Kimi Pro' }
|
|
678
|
+
],
|
|
679
|
+
'qwen': [
|
|
680
|
+
{ id: 'default', label: 'Default Model' },
|
|
681
|
+
{ id: 'qwen-7b', label: 'Qwen 7B' },
|
|
682
|
+
{ id: 'qwen-14b', label: 'Qwen 14B' }
|
|
683
|
+
],
|
|
684
|
+
'codex': [
|
|
685
|
+
{ id: 'default', label: 'Default Model' },
|
|
686
|
+
{ id: 'code-davinci-002', label: 'Code Davinci 002' },
|
|
687
|
+
{ id: 'code-cushman-001', label: 'Code Cushman 001' }
|
|
688
|
+
],
|
|
689
|
+
'mistral': [
|
|
690
|
+
{ id: 'default', label: 'Default Model' },
|
|
691
|
+
{ id: 'mistral-small', label: 'Mistral Small' },
|
|
692
|
+
{ id: 'mistral-medium', label: 'Mistral Medium' },
|
|
693
|
+
{ id: 'mistral-large', label: 'Mistral Large' }
|
|
694
|
+
],
|
|
695
|
+
'kiro': [
|
|
696
|
+
{ id: 'default', label: 'Default Model' },
|
|
697
|
+
{ id: 'kiro-1', label: 'Kiro 1' }
|
|
698
|
+
],
|
|
699
|
+
'fast-agent': [
|
|
700
|
+
{ id: 'default', label: 'Default Model' },
|
|
701
|
+
{ id: 'fast-agent-1', label: 'Fast Agent 1' }
|
|
588
702
|
]
|
|
589
703
|
};
|
|
590
704
|
|
|
591
705
|
if (fallbackModels[agentId]) {
|
|
592
|
-
|
|
593
|
-
|
|
706
|
+
const models = fallbackModels[agentId];
|
|
707
|
+
modelCache.set(agentId, { models, timestamp: Date.now() });
|
|
708
|
+
return models;
|
|
594
709
|
}
|
|
595
710
|
|
|
596
|
-
|
|
711
|
+
// Default fallback for any other agent
|
|
712
|
+
const defaultFallback = [
|
|
713
|
+
{ id: 'default', label: 'Default Model' }
|
|
714
|
+
];
|
|
715
|
+
modelCache.set(agentId, { models: defaultFallback, timestamp: Date.now() });
|
|
716
|
+
return defaultFallback;
|
|
597
717
|
}
|
|
598
718
|
|
|
599
719
|
const GEMINI_SCOPES = [
|
|
@@ -1892,8 +2012,31 @@ const server = http.createServer(async (req, res) => {
|
|
|
1892
2012
|
|
|
1893
2013
|
if (pathOnly === '/api/agents/search' && req.method === 'POST') {
|
|
1894
2014
|
const body = await parseBody(req);
|
|
1895
|
-
|
|
1896
|
-
|
|
2015
|
+
try {
|
|
2016
|
+
// Get local agents
|
|
2017
|
+
const localResult = queries.searchAgents(discoveredAgents, body);
|
|
2018
|
+
|
|
2019
|
+
// Get external agents from ACP servers
|
|
2020
|
+
const externalAgents = await discoverExternalACPServers();
|
|
2021
|
+
const externalResult = queries.searchAgents(externalAgents, body);
|
|
2022
|
+
|
|
2023
|
+
// Combine results
|
|
2024
|
+
const combinedAgents = [...localResult.agents, ...externalResult.agents];
|
|
2025
|
+
const total = localResult.total + externalResult.total;
|
|
2026
|
+
const hasMore = localResult.hasMore || externalResult.hasMore;
|
|
2027
|
+
|
|
2028
|
+
sendJSON(req, res, 200, {
|
|
2029
|
+
agents: combinedAgents,
|
|
2030
|
+
total,
|
|
2031
|
+
limit: body.limit || 50,
|
|
2032
|
+
offset: body.offset || 0,
|
|
2033
|
+
hasMore,
|
|
2034
|
+
});
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
console.error('Error searching agents:', error);
|
|
2037
|
+
const result = queries.searchAgents(discoveredAgents, body);
|
|
2038
|
+
sendJSON(req, res, 200, result);
|
|
2039
|
+
}
|
|
1897
2040
|
return;
|
|
1898
2041
|
}
|
|
1899
2042
|
|
package/static/index.html
CHANGED
|
@@ -804,25 +804,35 @@
|
|
|
804
804
|
align-items: flex-end;
|
|
805
805
|
}
|
|
806
806
|
|
|
807
|
-
.agent-selector {
|
|
808
|
-
padding: 0.5rem;
|
|
809
|
-
border: none;
|
|
810
|
-
border-radius: 0.375rem;
|
|
811
|
-
background-color: var(--color-bg-secondary);
|
|
812
|
-
color: var(--color-text-primary);
|
|
813
|
-
font-size: 0.8rem;
|
|
814
|
-
cursor: pointer;
|
|
815
|
-
flex-shrink: 0;
|
|
816
|
-
width: auto;
|
|
817
|
-
min-width: 80px;
|
|
818
|
-
max-width: 200px;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
.agent-selector
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
807
|
+
.agent-selector {
|
|
808
|
+
padding: 0.5rem;
|
|
809
|
+
border: none;
|
|
810
|
+
border-radius: 0.375rem;
|
|
811
|
+
background-color: var(--color-bg-secondary);
|
|
812
|
+
color: var(--color-text-primary);
|
|
813
|
+
font-size: 0.8rem;
|
|
814
|
+
cursor: pointer;
|
|
815
|
+
flex-shrink: 0;
|
|
816
|
+
width: auto;
|
|
817
|
+
min-width: 80px;
|
|
818
|
+
max-width: 200px;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.cli-agent-selector {
|
|
822
|
+
/* CLI agents have a different background color */
|
|
823
|
+
background-color: var(--color-bg-secondary);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
.acp-agent-selector {
|
|
827
|
+
/* ACP agents have a different background color */
|
|
828
|
+
background-color: var(--color-bg-secondary);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.agent-selector:disabled, .model-selector:disabled {
|
|
832
|
+
opacity: 0.5;
|
|
833
|
+
cursor: not-allowed;
|
|
834
|
+
background-color: var(--color-bg-secondary);
|
|
835
|
+
}
|
|
826
836
|
|
|
827
837
|
.model-selector {
|
|
828
838
|
padding: 0.5rem;
|
|
@@ -3167,17 +3177,18 @@
|
|
|
3167
3177
|
</div>
|
|
3168
3178
|
|
|
3169
3179
|
<!-- Input area: fixed at bottom -->
|
|
3170
|
-
<div class="input-section">
|
|
3171
|
-
<div class="input-wrapper">
|
|
3172
|
-
<select class="agent-selector" data-agent-selector title="Select agent"></select>
|
|
3173
|
-
<select class="
|
|
3174
|
-
<
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3180
|
+
<div class="input-section">
|
|
3181
|
+
<div class="input-wrapper">
|
|
3182
|
+
<select class="agent-selector cli-agent-selector" data-cli-agent-selector title="Select CLI agent"></select>
|
|
3183
|
+
<select class="agent-selector acp-agent-selector" data-acp-agent-selector title="Select ACP agent"></select>
|
|
3184
|
+
<select class="model-selector" data-model-selector title="Select model" data-empty="true"></select>
|
|
3185
|
+
<textarea
|
|
3186
|
+
class="message-textarea"
|
|
3187
|
+
data-message-input
|
|
3188
|
+
placeholder="Message AgentGUI... (Ctrl+Enter to send)"
|
|
3189
|
+
aria-label="Message input"
|
|
3190
|
+
rows="1"
|
|
3191
|
+
></textarea>
|
|
3181
3192
|
<button class="inject-btn" id="injectBtn" title="Inject instructions into running agent" aria-label="Inject instructions">
|
|
3182
3193
|
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
|
|
3183
3194
|
<path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/>
|
package/static/js/client.js
CHANGED
|
@@ -341,30 +341,50 @@ class AgentGUIClient {
|
|
|
341
341
|
});
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
/**
|
|
345
|
-
* Setup UI elements
|
|
346
|
-
*/
|
|
347
|
-
setupUI() {
|
|
348
|
-
const container = document.getElementById(this.config.containerId);
|
|
349
|
-
if (!container) {
|
|
350
|
-
throw new Error(`Container not found: ${this.config.containerId}`);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Get references to key UI elements
|
|
354
|
-
this.ui.statusIndicator = document.querySelector('[data-status-indicator]');
|
|
355
|
-
this.ui.messageInput = document.querySelector('[data-message-input]');
|
|
356
|
-
this.ui.sendButton = document.querySelector('[data-send-button]');
|
|
357
|
-
this.ui.
|
|
358
|
-
this.ui.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Setup UI elements
|
|
346
|
+
*/
|
|
347
|
+
setupUI() {
|
|
348
|
+
const container = document.getElementById(this.config.containerId);
|
|
349
|
+
if (!container) {
|
|
350
|
+
throw new Error(`Container not found: ${this.config.containerId}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Get references to key UI elements
|
|
354
|
+
this.ui.statusIndicator = document.querySelector('[data-status-indicator]');
|
|
355
|
+
this.ui.messageInput = document.querySelector('[data-message-input]');
|
|
356
|
+
this.ui.sendButton = document.querySelector('[data-send-button]');
|
|
357
|
+
this.ui.cliAgentSelector = document.querySelector('[data-cli-agent-selector]');
|
|
358
|
+
this.ui.acpAgentSelector = document.querySelector('[data-acp-agent-selector]');
|
|
359
|
+
this.ui.modelSelector = document.querySelector('[data-model-selector]');
|
|
360
|
+
|
|
361
|
+
if (this.ui.cliAgentSelector) {
|
|
362
|
+
this.ui.cliAgentSelector.addEventListener('change', () => {
|
|
363
|
+
if (!this._agentLocked) {
|
|
364
|
+
this.loadModelsForAgent(this.ui.cliAgentSelector.value);
|
|
365
|
+
this.saveAgentAndModelToConversation();
|
|
366
|
+
// Hide ACP selector when CLI is selected
|
|
367
|
+
if (this.ui.acpAgentSelector) {
|
|
368
|
+
this.ui.acpAgentSelector.value = '';
|
|
369
|
+
this.ui.acpAgentSelector.style.display = 'none';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (this.ui.acpAgentSelector) {
|
|
376
|
+
this.ui.acpAgentSelector.addEventListener('change', () => {
|
|
377
|
+
if (!this._agentLocked) {
|
|
378
|
+
this.loadModelsForAgent(this.ui.acpAgentSelector.value);
|
|
379
|
+
this.saveAgentAndModelToConversation();
|
|
380
|
+
// Hide CLI selector when ACP is selected
|
|
381
|
+
if (this.ui.cliAgentSelector) {
|
|
382
|
+
this.ui.cliAgentSelector.value = '';
|
|
383
|
+
this.ui.cliAgentSelector.style.display = 'none';
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
368
388
|
|
|
369
389
|
if (this.ui.modelSelector) {
|
|
370
390
|
this.ui.modelSelector.addEventListener('change', () => {
|
|
@@ -1244,12 +1264,12 @@ class AgentGUIClient {
|
|
|
1244
1264
|
}
|
|
1245
1265
|
}
|
|
1246
1266
|
|
|
1247
|
-
async startExecution() {
|
|
1248
|
-
const prompt = this.ui.messageInput?.value || '';
|
|
1249
|
-
const conv = this.state.currentConversation;
|
|
1250
|
-
const isNewConversation = conv && !conv.messageCount && !this.state.streamingConversations.has(conv.id);
|
|
1251
|
-
const agentId = (isNewConversation ? this.
|
|
1252
|
-
const model = this.ui.modelSelector?.value || null;
|
|
1267
|
+
async startExecution() {
|
|
1268
|
+
const prompt = this.ui.messageInput?.value || '';
|
|
1269
|
+
const conv = this.state.currentConversation;
|
|
1270
|
+
const isNewConversation = conv && !conv.messageCount && !this.state.streamingConversations.has(conv.id);
|
|
1271
|
+
const agentId = (isNewConversation ? this.getCurrentAgent() : null) || conv?.agentType || this.getCurrentAgent() || 'claude-code';
|
|
1272
|
+
const model = this.ui.modelSelector?.value || null;
|
|
1253
1273
|
|
|
1254
1274
|
if (!prompt.trim()) {
|
|
1255
1275
|
this.showError('Please enter a prompt');
|
|
@@ -1816,29 +1836,53 @@ class AgentGUIClient {
|
|
|
1816
1836
|
/**
|
|
1817
1837
|
* Load agents
|
|
1818
1838
|
*/
|
|
1819
|
-
async loadAgents() {
|
|
1820
|
-
return this._dedupedFetch('loadAgents', async () => {
|
|
1821
|
-
try {
|
|
1822
|
-
const { agents } = await window.wsClient.rpc('agent.ls');
|
|
1823
|
-
this.state.agents = agents;
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1839
|
+
async loadAgents() {
|
|
1840
|
+
return this._dedupedFetch('loadAgents', async () => {
|
|
1841
|
+
try {
|
|
1842
|
+
const { agents } = await window.wsClient.rpc('agent.ls');
|
|
1843
|
+
this.state.agents = agents;
|
|
1844
|
+
|
|
1845
|
+
// Split agents by protocol
|
|
1846
|
+
const cliAgents = agents.filter(agent => agent.protocol === 'cli');
|
|
1847
|
+
const acpAgents = agents.filter(agent => agent.protocol === 'acp');
|
|
1848
|
+
|
|
1849
|
+
// Populate CLI agent selector
|
|
1850
|
+
if (this.ui.cliAgentSelector) {
|
|
1851
|
+
if (cliAgents.length > 0) {
|
|
1852
|
+
this.ui.cliAgentSelector.innerHTML = cliAgents
|
|
1853
|
+
.map(agent => `<option value="${agent.id}">${agent.name}</option>`)
|
|
1854
|
+
.join('');
|
|
1855
|
+
this.ui.cliAgentSelector.style.display = 'inline-block';
|
|
1856
|
+
} else {
|
|
1857
|
+
this.ui.cliAgentSelector.innerHTML = '';
|
|
1858
|
+
this.ui.cliAgentSelector.style.display = 'none';
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// Populate ACP agent selector
|
|
1863
|
+
if (this.ui.acpAgentSelector) {
|
|
1864
|
+
if (acpAgents.length > 0) {
|
|
1865
|
+
this.ui.acpAgentSelector.innerHTML = acpAgents
|
|
1866
|
+
.map(agent => `<option value="${agent.id}">${agent.name}</option>`)
|
|
1867
|
+
.join('');
|
|
1868
|
+
this.ui.acpAgentSelector.style.display = 'inline-block';
|
|
1869
|
+
} else {
|
|
1870
|
+
this.ui.acpAgentSelector.innerHTML = '';
|
|
1871
|
+
this.ui.acpAgentSelector.style.display = 'none';
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
window.dispatchEvent(new CustomEvent('agents-loaded', { detail: { agents } }));
|
|
1876
|
+
if (agents.length > 0 && !this._agentLocked) {
|
|
1877
|
+
this.loadModelsForAgent(agents[0].id);
|
|
1878
|
+
}
|
|
1879
|
+
return agents;
|
|
1880
|
+
} catch (error) {
|
|
1881
|
+
console.error('Failed to load agents:', error);
|
|
1882
|
+
return [];
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1842
1886
|
|
|
1843
1887
|
async checkSpeechStatus() {
|
|
1844
1888
|
try {
|
|
@@ -1890,48 +1934,105 @@ class AgentGUIClient {
|
|
|
1890
1934
|
.join('');
|
|
1891
1935
|
}
|
|
1892
1936
|
|
|
1893
|
-
lockAgentAndModel(agentId, model) {
|
|
1894
|
-
this._agentLocked = true;
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
if (
|
|
1901
|
-
this.ui.
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1937
|
+
lockAgentAndModel(agentId, model) {
|
|
1938
|
+
this._agentLocked = true;
|
|
1939
|
+
|
|
1940
|
+
// Find the agent to determine protocol
|
|
1941
|
+
const agent = this.state.agents.find(a => a.id === agentId);
|
|
1942
|
+
|
|
1943
|
+
if (agent) {
|
|
1944
|
+
if (agent.protocol === 'cli') {
|
|
1945
|
+
if (this.ui.cliAgentSelector) {
|
|
1946
|
+
this.ui.cliAgentSelector.value = agentId;
|
|
1947
|
+
this.ui.cliAgentSelector.disabled = true;
|
|
1948
|
+
}
|
|
1949
|
+
if (this.ui.acpAgentSelector) {
|
|
1950
|
+
this.ui.acpAgentSelector.value = '';
|
|
1951
|
+
this.ui.acpAgentSelector.disabled = true;
|
|
1952
|
+
this.ui.acpAgentSelector.style.display = 'none';
|
|
1953
|
+
}
|
|
1954
|
+
} else {
|
|
1955
|
+
if (this.ui.acpAgentSelector) {
|
|
1956
|
+
this.ui.acpAgentSelector.value = agentId;
|
|
1957
|
+
this.ui.acpAgentSelector.disabled = true;
|
|
1958
|
+
}
|
|
1959
|
+
if (this.ui.cliAgentSelector) {
|
|
1960
|
+
this.ui.cliAgentSelector.value = '';
|
|
1961
|
+
this.ui.cliAgentSelector.disabled = true;
|
|
1962
|
+
this.ui.cliAgentSelector.style.display = 'none';
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
this.loadModelsForAgent(agentId).then(() => {
|
|
1968
|
+
if (this.ui.modelSelector && model) {
|
|
1969
|
+
this.ui.modelSelector.value = model;
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
unlockAgentAndModel() {
|
|
1975
|
+
this._agentLocked = false;
|
|
1976
|
+
if (this.ui.cliAgentSelector) {
|
|
1977
|
+
this.ui.cliAgentSelector.disabled = false;
|
|
1978
|
+
}
|
|
1979
|
+
if (this.ui.acpAgentSelector) {
|
|
1980
|
+
this.ui.acpAgentSelector.disabled = false;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Show both selectors again when unlocking
|
|
1984
|
+
if (this.ui.cliAgentSelector && this.state.agents.some(a => a.protocol === 'cli')) {
|
|
1985
|
+
this.ui.cliAgentSelector.style.display = 'inline-block';
|
|
1986
|
+
}
|
|
1987
|
+
if (this.ui.acpAgentSelector && this.state.agents.some(a => a.protocol === 'acp')) {
|
|
1988
|
+
this.ui.acpAgentSelector.style.display = 'inline-block';
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1912
1991
|
|
|
1913
1992
|
/**
|
|
1914
1993
|
* Apply agent and model selection based on conversation state
|
|
1915
1994
|
* Consolidates duplicate logic for cached and fresh conversation loads
|
|
1916
1995
|
*/
|
|
1917
|
-
applyAgentAndModelSelection(conversation, hasActivity) {
|
|
1918
|
-
const agentId = conversation.agentId || conversation.agentType || 'claude-code';
|
|
1919
|
-
const model = conversation.model || null;
|
|
1920
|
-
|
|
1921
|
-
if (hasActivity) {
|
|
1922
|
-
this.lockAgentAndModel(agentId, model);
|
|
1923
|
-
} else {
|
|
1924
|
-
this.unlockAgentAndModel();
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
if (
|
|
1930
|
-
this.ui.
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1996
|
+
applyAgentAndModelSelection(conversation, hasActivity) {
|
|
1997
|
+
const agentId = conversation.agentId || conversation.agentType || 'claude-code';
|
|
1998
|
+
const model = conversation.model || null;
|
|
1999
|
+
|
|
2000
|
+
if (hasActivity) {
|
|
2001
|
+
this.lockAgentAndModel(agentId, model);
|
|
2002
|
+
} else {
|
|
2003
|
+
this.unlockAgentAndModel();
|
|
2004
|
+
// Find the agent to determine protocol
|
|
2005
|
+
const agent = this.state.agents.find(a => a.id === agentId);
|
|
2006
|
+
|
|
2007
|
+
if (agent) {
|
|
2008
|
+
if (agent.protocol === 'cli') {
|
|
2009
|
+
if (this.ui.cliAgentSelector) {
|
|
2010
|
+
this.ui.cliAgentSelector.value = agentId;
|
|
2011
|
+
this.ui.cliAgentSelector.style.display = 'inline-block';
|
|
2012
|
+
}
|
|
2013
|
+
if (this.ui.acpAgentSelector) {
|
|
2014
|
+
this.ui.acpAgentSelector.value = '';
|
|
2015
|
+
this.ui.acpAgentSelector.style.display = 'none';
|
|
2016
|
+
}
|
|
2017
|
+
} else {
|
|
2018
|
+
if (this.ui.acpAgentSelector) {
|
|
2019
|
+
this.ui.acpAgentSelector.value = agentId;
|
|
2020
|
+
this.ui.acpAgentSelector.style.display = 'inline-block';
|
|
2021
|
+
}
|
|
2022
|
+
if (this.ui.cliAgentSelector) {
|
|
2023
|
+
this.ui.cliAgentSelector.value = '';
|
|
2024
|
+
this.ui.cliAgentSelector.style.display = 'none';
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
this.loadModelsForAgent(agentId).then(() => {
|
|
2030
|
+
if (model && this.ui.modelSelector) {
|
|
2031
|
+
this.ui.modelSelector.value = model;
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
1935
2036
|
|
|
1936
2037
|
/**
|
|
1937
2038
|
* Load conversations
|
|
@@ -2493,12 +2594,25 @@ class AgentGUIClient {
|
|
|
2493
2594
|
}
|
|
2494
2595
|
}
|
|
2495
2596
|
|
|
2496
|
-
/**
|
|
2497
|
-
* Get
|
|
2498
|
-
*/
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2597
|
+
/**
|
|
2598
|
+
* Get current selected agent
|
|
2599
|
+
*/
|
|
2600
|
+
getCurrentAgent() {
|
|
2601
|
+
if (this.ui.cliAgentSelector?.value) {
|
|
2602
|
+
return this.ui.cliAgentSelector.value;
|
|
2603
|
+
}
|
|
2604
|
+
if (this.ui.acpAgentSelector?.value) {
|
|
2605
|
+
return this.ui.acpAgentSelector.value;
|
|
2606
|
+
}
|
|
2607
|
+
return 'claude-code';
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
/**
|
|
2611
|
+
* Get current selected model
|
|
2612
|
+
*/
|
|
2613
|
+
getCurrentModel() {
|
|
2614
|
+
return this.ui.modelSelector?.value || null;
|
|
2615
|
+
}
|
|
2502
2616
|
|
|
2503
2617
|
/**
|
|
2504
2618
|
* Get metrics
|
package/static/js/terminal.js
CHANGED
|
@@ -1,106 +1,135 @@
|
|
|
1
|
-
(function() {
|
|
2
|
-
var ws = null;
|
|
3
|
-
var term = null;
|
|
4
|
-
var fitAddon = null;
|
|
5
|
-
var termActive = false;
|
|
6
|
-
var BASE = window.__BASE_URL || '';
|
|
7
|
-
|
|
8
|
-
function getWsUrl() {
|
|
9
|
-
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
10
|
-
return proto + '//' + location.host + BASE + '/sync';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function ensureTerm() {
|
|
14
|
-
var output = document.getElementById('terminalOutput');
|
|
15
|
-
if (!output) return false;
|
|
16
|
-
if (term) return true;
|
|
17
|
-
if (!window.Terminal || !window.FitAddon) return false;
|
|
18
|
-
|
|
19
|
-
term = new Terminal({
|
|
20
|
-
cursorBlink: true,
|
|
21
|
-
fontSize: 14,
|
|
22
|
-
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
23
|
-
theme: {
|
|
24
|
-
background: '#0d1117',
|
|
25
|
-
foreground: '#e6edf3',
|
|
26
|
-
cursor: '#e6edf3',
|
|
27
|
-
selectionBackground: '#3b4455'
|
|
28
|
-
},
|
|
29
|
-
convertEol: false,
|
|
30
|
-
scrollback: 5000
|
|
31
|
-
});
|
|
32
|
-
fitAddon = new FitAddon.FitAddon();
|
|
33
|
-
term.loadAddon(fitAddon);
|
|
34
|
-
output.innerHTML = '';
|
|
35
|
-
term.open(output);
|
|
36
|
-
fitAddon.fit();
|
|
37
|
-
|
|
38
|
-
term.onData(function(data) {
|
|
39
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
40
|
-
var encoded = btoa(unescape(encodeURIComponent(data)));
|
|
41
|
-
ws.send(JSON.stringify({ type: 'terminal_input', data: encoded }));
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
window.addEventListener('resize', function() {
|
|
46
|
-
if (fitAddon) try { fitAddon.fit(); } catch(_) {}
|
|
47
|
-
});
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function connectAndStart() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
startTerminal
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
(function() {
|
|
2
|
+
var ws = null;
|
|
3
|
+
var term = null;
|
|
4
|
+
var fitAddon = null;
|
|
5
|
+
var termActive = false;
|
|
6
|
+
var BASE = window.__BASE_URL || '';
|
|
7
|
+
|
|
8
|
+
function getWsUrl() {
|
|
9
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
10
|
+
return proto + '//' + location.host + BASE + '/sync';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ensureTerm() {
|
|
14
|
+
var output = document.getElementById('terminalOutput');
|
|
15
|
+
if (!output) return false;
|
|
16
|
+
if (term) return true;
|
|
17
|
+
if (!window.Terminal || !window.FitAddon) return false;
|
|
18
|
+
|
|
19
|
+
term = new Terminal({
|
|
20
|
+
cursorBlink: true,
|
|
21
|
+
fontSize: 14,
|
|
22
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
23
|
+
theme: {
|
|
24
|
+
background: '#0d1117',
|
|
25
|
+
foreground: '#e6edf3',
|
|
26
|
+
cursor: '#e6edf3',
|
|
27
|
+
selectionBackground: '#3b4455'
|
|
28
|
+
},
|
|
29
|
+
convertEol: false,
|
|
30
|
+
scrollback: 5000
|
|
31
|
+
});
|
|
32
|
+
fitAddon = new FitAddon.FitAddon();
|
|
33
|
+
term.loadAddon(fitAddon);
|
|
34
|
+
output.innerHTML = '';
|
|
35
|
+
term.open(output);
|
|
36
|
+
fitAddon.fit();
|
|
37
|
+
|
|
38
|
+
term.onData(function(data) {
|
|
39
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
40
|
+
var encoded = btoa(unescape(encodeURIComponent(data)));
|
|
41
|
+
ws.send(JSON.stringify({ type: 'terminal_input', data: encoded }));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
window.addEventListener('resize', function() {
|
|
46
|
+
if (fitAddon) try { fitAddon.fit(); } catch(_) {}
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function connectAndStart() {
|
|
52
|
+
console.log('Terminal: Connecting to WebSocket');
|
|
53
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
54
|
+
console.log('Terminal: Sending terminal_start command');
|
|
55
|
+
ws.send(JSON.stringify({ type: 'terminal_start', cwd: window.__STARTUP_CWD || undefined }));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (ws && ws.readyState === WebSocket.CONNECTING) {
|
|
59
|
+
console.log('Terminal: WebSocket already connecting');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ws = new WebSocket(getWsUrl());
|
|
64
|
+
ws.onopen = function() {
|
|
65
|
+
console.log('Terminal: WebSocket connected, starting terminal');
|
|
66
|
+
ws.send(JSON.stringify({ type: 'terminal_start', cwd: window.__STARTUP_CWD || undefined }));
|
|
67
|
+
};
|
|
68
|
+
ws.onmessage = function(e) {
|
|
69
|
+
try {
|
|
70
|
+
var msg = JSON.parse(e.data);
|
|
71
|
+
if (msg.type === 'terminal_output' && term) {
|
|
72
|
+
var raw = msg.encoding === 'base64'
|
|
73
|
+
? decodeURIComponent(escape(atob(msg.data)))
|
|
74
|
+
: msg.data;
|
|
75
|
+
term.write(raw);
|
|
76
|
+
} else if (msg.type === 'terminal_exit' && term) {
|
|
77
|
+
term.write('\r\n[Process exited with code ' + msg.code + ']\r\n');
|
|
78
|
+
if (termActive) setTimeout(connectAndStart, 2000);
|
|
79
|
+
} else if (msg.type === 'terminal_started') {
|
|
80
|
+
console.log('Terminal: Started successfully');
|
|
81
|
+
}
|
|
82
|
+
} catch(_) {}
|
|
83
|
+
};
|
|
84
|
+
ws.onclose = function() {
|
|
85
|
+
console.log('Terminal: WebSocket closed');
|
|
86
|
+
ws = null;
|
|
87
|
+
if (termActive) setTimeout(connectAndStart, 2000);
|
|
88
|
+
};
|
|
89
|
+
ws.onerror = function(error) {
|
|
90
|
+
console.error('Terminal: WebSocket error:', error);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function startTerminal() {
|
|
95
|
+
console.log('Terminal: Starting terminal module');
|
|
96
|
+
if (!ensureTerm()) {
|
|
97
|
+
console.log('Terminal: Terminal not ready, retrying');
|
|
98
|
+
setTimeout(startTerminal, 200);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
termActive = true;
|
|
102
|
+
connectAndStart();
|
|
103
|
+
setTimeout(function() { if (fitAddon) try { fitAddon.fit(); } catch(_) {} }, 100);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function stopTerminal() {
|
|
107
|
+
console.log('Terminal: Stopping terminal module');
|
|
108
|
+
termActive = false;
|
|
109
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
110
|
+
ws.send(JSON.stringify({ type: 'terminal_stop' }));
|
|
111
|
+
}
|
|
112
|
+
if (ws) {
|
|
113
|
+
ws.close();
|
|
114
|
+
ws = null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check if terminal view is initially active
|
|
119
|
+
if (document.getElementById('terminalContainer') &&
|
|
120
|
+
window.getComputedStyle(document.getElementById('terminalContainer')).display !== 'none') {
|
|
121
|
+
console.log('Terminal: Terminal view initially visible, starting');
|
|
122
|
+
startTerminal();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
window.addEventListener('view-switched', function(e) {
|
|
126
|
+
if (e.detail.view === 'terminal') {
|
|
127
|
+
startTerminal();
|
|
128
|
+
} else if (termActive) {
|
|
129
|
+
stopTerminal();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
104
133
|
window.terminalModule = {
|
|
105
134
|
start: startTerminal,
|
|
106
135
|
stop: stopTerminal,
|