agentgui 1.0.220 → 1.0.222
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/database.js +7 -5
- package/lib/claude-runner.js +14 -6
- package/package.json +1 -1
- package/server.js +74 -14
- package/static/index.html +24 -0
- package/static/js/client.js +122 -17
- package/static/js/conversations.js +6 -2
package/database.js
CHANGED
|
@@ -228,7 +228,8 @@ try {
|
|
|
228
228
|
lastSyncedAt: 'INTEGER',
|
|
229
229
|
workingDirectory: 'TEXT',
|
|
230
230
|
claudeSessionId: 'TEXT',
|
|
231
|
-
isStreaming: 'INTEGER DEFAULT 0'
|
|
231
|
+
isStreaming: 'INTEGER DEFAULT 0',
|
|
232
|
+
model: 'TEXT'
|
|
232
233
|
};
|
|
233
234
|
|
|
234
235
|
let addedColumns = false;
|
|
@@ -270,19 +271,20 @@ function generateId(prefix) {
|
|
|
270
271
|
|
|
271
272
|
export const queries = {
|
|
272
273
|
_db: db,
|
|
273
|
-
createConversation(agentType, title = null, workingDirectory = null) {
|
|
274
|
+
createConversation(agentType, title = null, workingDirectory = null, model = null) {
|
|
274
275
|
const id = generateId('conv');
|
|
275
276
|
const now = Date.now();
|
|
276
277
|
const stmt = prep(
|
|
277
|
-
`INSERT INTO conversations (id, agentId, agentType, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
278
|
+
`INSERT INTO conversations (id, agentId, agentType, title, created_at, updated_at, status, workingDirectory, model) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
278
279
|
);
|
|
279
|
-
stmt.run(id, agentType, agentType, title, now, now, 'active', workingDirectory);
|
|
280
|
+
stmt.run(id, agentType, agentType, title, now, now, 'active', workingDirectory, model);
|
|
280
281
|
|
|
281
282
|
return {
|
|
282
283
|
id,
|
|
283
284
|
agentType,
|
|
284
285
|
title,
|
|
285
286
|
workingDirectory,
|
|
287
|
+
model,
|
|
286
288
|
created_at: now,
|
|
287
289
|
updated_at: now,
|
|
288
290
|
status: 'active'
|
|
@@ -301,7 +303,7 @@ export const queries = {
|
|
|
301
303
|
|
|
302
304
|
getConversationsList() {
|
|
303
305
|
const stmt = prep(
|
|
304
|
-
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
306
|
+
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming, model FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
305
307
|
);
|
|
306
308
|
return stmt.all('deleted');
|
|
307
309
|
},
|
package/lib/claude-runner.js
CHANGED
|
@@ -306,14 +306,16 @@ class AgentRunner {
|
|
|
306
306
|
if (initialized && !sessionCreated) {
|
|
307
307
|
sessionCreated = true;
|
|
308
308
|
|
|
309
|
+
const sessionParams = {
|
|
310
|
+
cwd: cwd,
|
|
311
|
+
mcpServers: []
|
|
312
|
+
};
|
|
313
|
+
if (config.model) sessionParams.model = config.model;
|
|
309
314
|
const sessionRequest = {
|
|
310
315
|
jsonrpc: '2.0',
|
|
311
316
|
id: ++requestId,
|
|
312
317
|
method: 'session/new',
|
|
313
|
-
params:
|
|
314
|
-
cwd: cwd,
|
|
315
|
-
mcpServers: []
|
|
316
|
-
}
|
|
318
|
+
params: sessionParams
|
|
317
319
|
};
|
|
318
320
|
proc.stdin.write(JSON.stringify(sessionRequest) + '\n');
|
|
319
321
|
} else if (!initialized) {
|
|
@@ -482,7 +484,8 @@ registry.register({
|
|
|
482
484
|
outputFormat = 'stream-json',
|
|
483
485
|
print = true,
|
|
484
486
|
resumeSessionId = null,
|
|
485
|
-
systemPrompt = null
|
|
487
|
+
systemPrompt = null,
|
|
488
|
+
model = null
|
|
486
489
|
} = config;
|
|
487
490
|
|
|
488
491
|
const flags = [];
|
|
@@ -490,6 +493,7 @@ registry.register({
|
|
|
490
493
|
if (verbose) flags.push('--verbose');
|
|
491
494
|
flags.push(`--output-format=${outputFormat}`);
|
|
492
495
|
flags.push('--dangerously-skip-permissions');
|
|
496
|
+
if (model) flags.push('--model', model);
|
|
493
497
|
if (resumeSessionId) flags.push('--resume', resumeSessionId);
|
|
494
498
|
if (systemPrompt) flags.push('--append-system-prompt', systemPrompt);
|
|
495
499
|
|
|
@@ -735,7 +739,11 @@ registry.register({
|
|
|
735
739
|
protocol: 'acp',
|
|
736
740
|
supportsStdin: false,
|
|
737
741
|
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
738
|
-
buildArgs
|
|
742
|
+
buildArgs(prompt, config) {
|
|
743
|
+
const args = ['--experimental-acp', '--yolo'];
|
|
744
|
+
if (config?.model) args.push('--model', config.model);
|
|
745
|
+
return args;
|
|
746
|
+
},
|
|
739
747
|
protocolHandler: acpProtocolHandler
|
|
740
748
|
});
|
|
741
749
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -202,6 +202,44 @@ function discoverAgents() {
|
|
|
202
202
|
|
|
203
203
|
const discoveredAgents = discoverAgents();
|
|
204
204
|
|
|
205
|
+
const modelCache = new Map();
|
|
206
|
+
|
|
207
|
+
async function getModelsForAgent(agentId) {
|
|
208
|
+
if (agentId === 'claude-code') {
|
|
209
|
+
return [
|
|
210
|
+
{ id: '', label: 'Default' },
|
|
211
|
+
{ id: 'sonnet', label: 'Sonnet' },
|
|
212
|
+
{ id: 'opus', label: 'Opus' },
|
|
213
|
+
{ id: 'haiku', label: 'Haiku' },
|
|
214
|
+
{ id: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' },
|
|
215
|
+
{ id: 'claude-opus-4-6', label: 'Opus 4.6' },
|
|
216
|
+
{ id: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
if (agentId === 'opencode') {
|
|
220
|
+
try {
|
|
221
|
+
const result = execSync('opencode models 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });
|
|
222
|
+
const lines = result.split('\n').map(l => l.trim()).filter(Boolean);
|
|
223
|
+
const models = [{ id: '', label: 'Default' }];
|
|
224
|
+
for (const line of lines) {
|
|
225
|
+
models.push({ id: line, label: line });
|
|
226
|
+
}
|
|
227
|
+
return models;
|
|
228
|
+
} catch (_) {
|
|
229
|
+
return [{ id: '', label: 'Default' }];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (agentId === 'gemini') {
|
|
233
|
+
return [
|
|
234
|
+
{ id: '', label: 'Default' },
|
|
235
|
+
{ id: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
|
|
236
|
+
{ id: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
|
237
|
+
{ id: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' }
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
|
|
205
243
|
const GEMINI_SCOPES = [
|
|
206
244
|
'https://www.googleapis.com/auth/cloud-platform',
|
|
207
245
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
@@ -722,8 +760,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
722
760
|
|
|
723
761
|
if (pathOnly === '/api/conversations' && req.method === 'POST') {
|
|
724
762
|
const body = await parseBody(req);
|
|
725
|
-
const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null);
|
|
726
|
-
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory }, conversation.id);
|
|
763
|
+
const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null, body.model || null);
|
|
764
|
+
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory, model: conversation.model }, conversation.id);
|
|
727
765
|
broadcastSync({ type: 'conversation_created', conversation });
|
|
728
766
|
sendJSON(req, res, 201, { conversation });
|
|
729
767
|
return;
|
|
@@ -782,6 +820,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
782
820
|
if (!conv) { sendJSON(req, res, 404, { error: 'Conversation not found' }); return; }
|
|
783
821
|
const body = await parseBody(req);
|
|
784
822
|
const agentId = body.agentId || conv.agentType || conv.agentId || 'claude-code';
|
|
823
|
+
const model = body.model || conv.model || null;
|
|
785
824
|
const idempotencyKey = body.idempotencyKey || null;
|
|
786
825
|
const message = queries.createMessage(conversationId, 'user', body.content, idempotencyKey);
|
|
787
826
|
queries.createEvent('message.created', { role: 'user', messageId: message.id }, conversationId);
|
|
@@ -789,7 +828,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
789
828
|
|
|
790
829
|
if (activeExecutions.has(conversationId)) {
|
|
791
830
|
if (!messageQueues.has(conversationId)) messageQueues.set(conversationId, []);
|
|
792
|
-
messageQueues.get(conversationId).push({ content: body.content, agentId, messageId: message.id });
|
|
831
|
+
messageQueues.get(conversationId).push({ content: body.content, agentId, model, messageId: message.id });
|
|
793
832
|
const queueLength = messageQueues.get(conversationId).length;
|
|
794
833
|
broadcastSync({ type: 'queue_status', conversationId, queueLength, messageId: message.id, timestamp: Date.now() });
|
|
795
834
|
sendJSON(req, res, 200, { message, queued: true, queuePosition: queueLength, idempotencyKey });
|
|
@@ -813,7 +852,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
813
852
|
|
|
814
853
|
sendJSON(req, res, 201, { message, session, idempotencyKey });
|
|
815
854
|
|
|
816
|
-
processMessageWithStreaming(conversationId, message.id, session.id, body.content, agentId)
|
|
855
|
+
processMessageWithStreaming(conversationId, message.id, session.id, body.content, agentId, model)
|
|
817
856
|
.catch(err => {
|
|
818
857
|
console.error(`[messages] Uncaught error for conv ${conversationId}:`, err.message);
|
|
819
858
|
debugLog(`[messages] Uncaught error: ${err.message}`);
|
|
@@ -831,6 +870,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
831
870
|
|
|
832
871
|
const prompt = body.content || body.message || '';
|
|
833
872
|
const agentId = body.agentId || conv.agentType || conv.agentId || 'claude-code';
|
|
873
|
+
const model = body.model || conv.model || null;
|
|
834
874
|
|
|
835
875
|
const userMessage = queries.createMessage(conversationId, 'user', prompt);
|
|
836
876
|
queries.createEvent('message.created', { role: 'user', messageId: userMessage.id }, conversationId);
|
|
@@ -840,7 +880,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
840
880
|
if (activeExecutions.has(conversationId)) {
|
|
841
881
|
debugLog(`[stream] Conversation ${conversationId} is busy, queuing message`);
|
|
842
882
|
if (!messageQueues.has(conversationId)) messageQueues.set(conversationId, []);
|
|
843
|
-
messageQueues.get(conversationId).push({ content: prompt, agentId, messageId: userMessage.id });
|
|
883
|
+
messageQueues.get(conversationId).push({ content: prompt, agentId, model, messageId: userMessage.id });
|
|
844
884
|
|
|
845
885
|
const queueLength = messageQueues.get(conversationId).length;
|
|
846
886
|
broadcastSync({ type: 'queue_status', conversationId, queueLength, messageId: userMessage.id, timestamp: Date.now() });
|
|
@@ -866,7 +906,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
866
906
|
|
|
867
907
|
sendJSON(req, res, 200, { message: userMessage, session, streamId: session.id });
|
|
868
908
|
|
|
869
|
-
processMessageWithStreaming(conversationId, userMessage.id, session.id, prompt, agentId)
|
|
909
|
+
processMessageWithStreaming(conversationId, userMessage.id, session.id, prompt, agentId, model)
|
|
870
910
|
.catch(err => debugLog(`[stream] Uncaught error: ${err.message}`));
|
|
871
911
|
return;
|
|
872
912
|
}
|
|
@@ -1127,6 +1167,24 @@ const server = http.createServer(async (req, res) => {
|
|
|
1127
1167
|
return;
|
|
1128
1168
|
}
|
|
1129
1169
|
|
|
1170
|
+
const modelsMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/models$/);
|
|
1171
|
+
if (modelsMatch && req.method === 'GET') {
|
|
1172
|
+
const agentId = modelsMatch[1];
|
|
1173
|
+
const cached = modelCache.get(agentId);
|
|
1174
|
+
if (cached && (Date.now() - cached.ts) < 300000) {
|
|
1175
|
+
sendJSON(req, res, 200, { models: cached.models });
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
const models = await getModelsForAgent(agentId);
|
|
1180
|
+
modelCache.set(agentId, { models, ts: Date.now() });
|
|
1181
|
+
sendJSON(req, res, 200, { models });
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
sendJSON(req, res, 200, { models: [] });
|
|
1184
|
+
}
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1130
1188
|
if (pathOnly === '/api/agents/auth-status' && req.method === 'GET') {
|
|
1131
1189
|
const statuses = discoveredAgents.map(agent => {
|
|
1132
1190
|
const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
|
|
@@ -1707,7 +1765,7 @@ function createChunkBatcher() {
|
|
|
1707
1765
|
return { add, drain };
|
|
1708
1766
|
}
|
|
1709
1767
|
|
|
1710
|
-
async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId) {
|
|
1768
|
+
async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId, model) {
|
|
1711
1769
|
const startTime = Date.now();
|
|
1712
1770
|
|
|
1713
1771
|
const conv = queries.getConversation(conversationId);
|
|
@@ -1844,6 +1902,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1844
1902
|
}
|
|
1845
1903
|
};
|
|
1846
1904
|
|
|
1905
|
+
const resolvedModel = model || conv?.model || null;
|
|
1847
1906
|
const config = {
|
|
1848
1907
|
verbose: true,
|
|
1849
1908
|
outputFormat: 'stream-json',
|
|
@@ -1851,6 +1910,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1851
1910
|
print: true,
|
|
1852
1911
|
resumeSessionId,
|
|
1853
1912
|
systemPrompt: SYSTEM_PROMPT,
|
|
1913
|
+
model: resolvedModel || undefined,
|
|
1854
1914
|
onEvent,
|
|
1855
1915
|
onPid: (pid) => {
|
|
1856
1916
|
const entry = activeExecutions.get(conversationId);
|
|
@@ -1952,7 +2012,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1952
2012
|
conversationId,
|
|
1953
2013
|
timestamp: Date.now()
|
|
1954
2014
|
});
|
|
1955
|
-
scheduleRetry(conversationId, messageId, content, agentId);
|
|
2015
|
+
scheduleRetry(conversationId, messageId, content, agentId, model);
|
|
1956
2016
|
}, cooldownMs);
|
|
1957
2017
|
return;
|
|
1958
2018
|
}
|
|
@@ -1983,16 +2043,16 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1983
2043
|
}
|
|
1984
2044
|
}
|
|
1985
2045
|
|
|
1986
|
-
function scheduleRetry(conversationId, messageId, content, agentId) {
|
|
2046
|
+
function scheduleRetry(conversationId, messageId, content, agentId, model) {
|
|
1987
2047
|
debugLog(`[rate-limit] scheduleRetry called for conv ${conversationId}, messageId=${messageId}`);
|
|
1988
|
-
|
|
2048
|
+
|
|
1989
2049
|
if (!content) {
|
|
1990
2050
|
const conv = queries.getConversation(conversationId);
|
|
1991
2051
|
const lastMsg = queries.getLastUserMessage(conversationId);
|
|
1992
2052
|
content = lastMsg?.content || 'continue';
|
|
1993
2053
|
debugLog(`[rate-limit] Recovered content from last message: ${content?.substring?.(0, 50)}...`);
|
|
1994
2054
|
}
|
|
1995
|
-
|
|
2055
|
+
|
|
1996
2056
|
const newSession = queries.createSession(conversationId);
|
|
1997
2057
|
queries.createEvent('session.created', { messageId, sessionId: newSession.id, retryReason: 'rate_limit' }, conversationId, newSession.id);
|
|
1998
2058
|
|
|
@@ -2007,7 +2067,7 @@ function scheduleRetry(conversationId, messageId, content, agentId) {
|
|
|
2007
2067
|
});
|
|
2008
2068
|
|
|
2009
2069
|
debugLog(`[rate-limit] Calling processMessageWithStreaming for retry`);
|
|
2010
|
-
processMessageWithStreaming(conversationId, messageId, newSession.id, content, agentId)
|
|
2070
|
+
processMessageWithStreaming(conversationId, messageId, newSession.id, content, agentId, model)
|
|
2011
2071
|
.catch(err => {
|
|
2012
2072
|
debugLog(`[rate-limit] Retry failed: ${err.message}`);
|
|
2013
2073
|
console.error(`[rate-limit] Retry error for conv ${conversationId}:`, err);
|
|
@@ -2042,7 +2102,7 @@ function drainMessageQueue(conversationId) {
|
|
|
2042
2102
|
timestamp: Date.now()
|
|
2043
2103
|
});
|
|
2044
2104
|
|
|
2045
|
-
processMessageWithStreaming(conversationId, next.messageId, session.id, next.content, next.agentId)
|
|
2105
|
+
processMessageWithStreaming(conversationId, next.messageId, session.id, next.content, next.agentId, next.model)
|
|
2046
2106
|
.catch(err => debugLog(`[queue] Error processing queued message: ${err.message}`));
|
|
2047
2107
|
}
|
|
2048
2108
|
|
|
@@ -2356,7 +2416,7 @@ async function resumeInterruptedStreams() {
|
|
|
2356
2416
|
const messageId = lastMsg?.id || null;
|
|
2357
2417
|
console.log(`[RESUME] Resuming conv ${conv.id} (claude session: ${conv.claudeSessionId})`);
|
|
2358
2418
|
|
|
2359
|
-
processMessageWithStreaming(conv.id, messageId, session.id, promptText, conv.agentType)
|
|
2419
|
+
processMessageWithStreaming(conv.id, messageId, session.id, promptText, conv.agentType, conv.model)
|
|
2360
2420
|
.catch(err => debugLog(`[RESUME] Error resuming conv ${conv.id}: ${err.message}`));
|
|
2361
2421
|
|
|
2362
2422
|
if (i < toResume.length - 1) {
|
package/static/index.html
CHANGED
|
@@ -816,6 +816,28 @@
|
|
|
816
816
|
flex-shrink: 0;
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
+
.agent-selector:disabled, .model-selector:disabled {
|
|
820
|
+
opacity: 0.5;
|
|
821
|
+
cursor: not-allowed;
|
|
822
|
+
background-color: var(--color-bg-secondary);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.model-selector {
|
|
826
|
+
padding: 0.5rem;
|
|
827
|
+
border: none;
|
|
828
|
+
border-radius: 0.375rem;
|
|
829
|
+
background-color: var(--color-bg-secondary);
|
|
830
|
+
color: var(--color-text-primary);
|
|
831
|
+
font-size: 0.8rem;
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
flex-shrink: 0;
|
|
834
|
+
max-width: 160px;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.model-selector:empty, .model-selector[data-empty="true"] {
|
|
838
|
+
display: none;
|
|
839
|
+
}
|
|
840
|
+
|
|
819
841
|
.message-textarea {
|
|
820
842
|
flex: 1;
|
|
821
843
|
padding: 0.625rem 0.875rem;
|
|
@@ -1005,6 +1027,7 @@
|
|
|
1005
1027
|
.messages-wrapper { padding: 0.375rem 0.5rem; }
|
|
1006
1028
|
.input-section { padding: 0.5rem; padding-bottom: calc(0.5rem + env(safe-area-inset-bottom)); }
|
|
1007
1029
|
.agent-selector { display: none; }
|
|
1030
|
+
.model-selector { display: none; }
|
|
1008
1031
|
}
|
|
1009
1032
|
|
|
1010
1033
|
/* ===== SCROLLBAR STYLING ===== */
|
|
@@ -2456,6 +2479,7 @@
|
|
|
2456
2479
|
<div class="input-section">
|
|
2457
2480
|
<div class="input-wrapper">
|
|
2458
2481
|
<select class="agent-selector" data-agent-selector title="Select agent"></select>
|
|
2482
|
+
<select class="model-selector" data-model-selector title="Select model" data-empty="true"></select>
|
|
2459
2483
|
<textarea
|
|
2460
2484
|
class="message-textarea"
|
|
2461
2485
|
data-message-input
|
package/static/js/client.js
CHANGED
|
@@ -42,9 +42,13 @@ class AgentGUIClient {
|
|
|
42
42
|
statusIndicator: null,
|
|
43
43
|
messageInput: null,
|
|
44
44
|
sendButton: null,
|
|
45
|
-
agentSelector: null
|
|
45
|
+
agentSelector: null,
|
|
46
|
+
modelSelector: null
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
this._agentLocked = false;
|
|
50
|
+
this._modelCache = new Map();
|
|
51
|
+
|
|
48
52
|
this.chunkPollState = {
|
|
49
53
|
isPolling: false,
|
|
50
54
|
lastFetchTimestamp: 0,
|
|
@@ -322,6 +326,15 @@ class AgentGUIClient {
|
|
|
322
326
|
this.ui.messageInput = document.querySelector('[data-message-input]');
|
|
323
327
|
this.ui.sendButton = document.querySelector('[data-send-button]');
|
|
324
328
|
this.ui.agentSelector = document.querySelector('[data-agent-selector]');
|
|
329
|
+
this.ui.modelSelector = document.querySelector('[data-model-selector]');
|
|
330
|
+
|
|
331
|
+
if (this.ui.agentSelector) {
|
|
332
|
+
this.ui.agentSelector.addEventListener('change', () => {
|
|
333
|
+
if (!this._agentLocked) {
|
|
334
|
+
this.loadModelsForAgent(this.ui.agentSelector.value);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
325
338
|
|
|
326
339
|
// Setup event listeners
|
|
327
340
|
if (this.ui.sendButton) {
|
|
@@ -352,10 +365,15 @@ class AgentGUIClient {
|
|
|
352
365
|
this.setupScrollTracking();
|
|
353
366
|
|
|
354
367
|
window.addEventListener('create-new-conversation', (event) => {
|
|
368
|
+
this.unlockAgentAndModel();
|
|
355
369
|
const detail = event.detail || {};
|
|
356
370
|
this.createNewConversation(detail.workingDirectory, detail.title);
|
|
357
371
|
});
|
|
358
372
|
|
|
373
|
+
window.addEventListener('preparing-new-conversation', () => {
|
|
374
|
+
this.unlockAgentAndModel();
|
|
375
|
+
});
|
|
376
|
+
|
|
359
377
|
// Listen for conversation selection
|
|
360
378
|
window.addEventListener('conversation-selected', (event) => {
|
|
361
379
|
const conversationId = event.detail.conversationId;
|
|
@@ -479,7 +497,7 @@ class AgentGUIClient {
|
|
|
479
497
|
outputEl.innerHTML = `
|
|
480
498
|
<div class="conversation-header">
|
|
481
499
|
<h2>${this.escapeHtml(conv?.title || 'Conversation')}</h2>
|
|
482
|
-
<p class="text-secondary">${conv?.agentType || 'unknown'} - ${new Date(conv?.created_at || Date.now()).toLocaleDateString()}${wdInfo}</p>
|
|
500
|
+
<p class="text-secondary">${conv?.agentType || 'unknown'}${conv?.model ? ' (' + this.escapeHtml(conv.model) + ')' : ''} - ${new Date(conv?.created_at || Date.now()).toLocaleDateString()}${wdInfo}</p>
|
|
483
501
|
</div>
|
|
484
502
|
<div class="conversation-messages"></div>
|
|
485
503
|
`;
|
|
@@ -1098,7 +1116,10 @@ class AgentGUIClient {
|
|
|
1098
1116
|
|
|
1099
1117
|
async startExecution() {
|
|
1100
1118
|
const prompt = this.ui.messageInput?.value || '';
|
|
1101
|
-
const
|
|
1119
|
+
const conv = this.state.currentConversation;
|
|
1120
|
+
const isNewConversation = conv && !conv.messageCount && !this.state.streamingConversations.has(conv.id);
|
|
1121
|
+
const agentId = (isNewConversation ? this.ui.agentSelector?.value : null) || conv?.agentType || this.ui.agentSelector?.value || 'claude-code';
|
|
1122
|
+
const model = this.ui.modelSelector?.value || null;
|
|
1102
1123
|
|
|
1103
1124
|
if (!prompt.trim()) {
|
|
1104
1125
|
this.showError('Please enter a prompt');
|
|
@@ -1116,24 +1137,28 @@ class AgentGUIClient {
|
|
|
1116
1137
|
this.disableControls();
|
|
1117
1138
|
|
|
1118
1139
|
try {
|
|
1119
|
-
if (
|
|
1120
|
-
|
|
1140
|
+
if (conv?.id) {
|
|
1141
|
+
this.lockAgentAndModel(agentId, model);
|
|
1142
|
+
await this.streamToConversation(conv.id, savedPrompt, agentId, model);
|
|
1121
1143
|
this._confirmOptimisticMessage(pendingId);
|
|
1122
1144
|
} else {
|
|
1145
|
+
const body = { agentId, title: savedPrompt.substring(0, 50) };
|
|
1146
|
+
if (model) body.model = model;
|
|
1123
1147
|
const response = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
1124
1148
|
method: 'POST',
|
|
1125
1149
|
headers: { 'Content-Type': 'application/json' },
|
|
1126
|
-
body: JSON.stringify(
|
|
1150
|
+
body: JSON.stringify(body)
|
|
1127
1151
|
});
|
|
1128
1152
|
const { conversation } = await response.json();
|
|
1129
1153
|
this.state.currentConversation = conversation;
|
|
1154
|
+
this.lockAgentAndModel(agentId, model);
|
|
1130
1155
|
|
|
1131
1156
|
if (window.conversationManager) {
|
|
1132
1157
|
window.conversationManager.loadConversations();
|
|
1133
1158
|
window.conversationManager.select(conversation.id);
|
|
1134
1159
|
}
|
|
1135
1160
|
|
|
1136
|
-
await this.streamToConversation(conversation.id, savedPrompt, agentId);
|
|
1161
|
+
await this.streamToConversation(conversation.id, savedPrompt, agentId, model);
|
|
1137
1162
|
this._confirmOptimisticMessage(pendingId);
|
|
1138
1163
|
}
|
|
1139
1164
|
} catch (error) {
|
|
@@ -1361,7 +1386,7 @@ class AgentGUIClient {
|
|
|
1361
1386
|
outputEl.innerHTML = `
|
|
1362
1387
|
<div class="conversation-header">
|
|
1363
1388
|
<h2>${this.escapeHtml(title)}</h2>
|
|
1364
|
-
<p class="text-secondary">${conv?.agentType || 'unknown'} - ${conv ? new Date(conv.created_at).toLocaleDateString() : ''}${wdInfo}</p>
|
|
1389
|
+
<p class="text-secondary">${conv?.agentType || 'unknown'}${conv?.model ? ' (' + this.escapeHtml(conv.model) + ')' : ''} - ${conv ? new Date(conv.created_at).toLocaleDateString() : ''}${wdInfo}</p>
|
|
1365
1390
|
</div>
|
|
1366
1391
|
<div class="conversation-messages">
|
|
1367
1392
|
<div class="skeleton-loading">
|
|
@@ -1374,29 +1399,33 @@ class AgentGUIClient {
|
|
|
1374
1399
|
`;
|
|
1375
1400
|
}
|
|
1376
1401
|
|
|
1377
|
-
async streamToConversation(conversationId, prompt, agentId) {
|
|
1402
|
+
async streamToConversation(conversationId, prompt, agentId, model) {
|
|
1378
1403
|
try {
|
|
1379
1404
|
if (this.wsManager.isConnected) {
|
|
1380
1405
|
this.wsManager.sendMessage({ type: 'subscribe', conversationId });
|
|
1381
1406
|
}
|
|
1382
1407
|
|
|
1408
|
+
const streamBody = { content: prompt, agentId };
|
|
1409
|
+
if (model) streamBody.model = model;
|
|
1383
1410
|
const response = await fetch(`${window.__BASE_URL}/api/conversations/${conversationId}/stream`, {
|
|
1384
1411
|
method: 'POST',
|
|
1385
1412
|
headers: { 'Content-Type': 'application/json' },
|
|
1386
|
-
body: JSON.stringify(
|
|
1413
|
+
body: JSON.stringify(streamBody)
|
|
1387
1414
|
});
|
|
1388
1415
|
|
|
1389
1416
|
if (response.status === 404) {
|
|
1390
1417
|
console.warn('Conversation not found, recreating:', conversationId);
|
|
1391
1418
|
const conv = this.state.currentConversation;
|
|
1419
|
+
const createBody = {
|
|
1420
|
+
agentId,
|
|
1421
|
+
title: conv?.title || prompt.substring(0, 50),
|
|
1422
|
+
workingDirectory: conv?.workingDirectory || null
|
|
1423
|
+
};
|
|
1424
|
+
if (model) createBody.model = model;
|
|
1392
1425
|
const createResp = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
1393
1426
|
method: 'POST',
|
|
1394
1427
|
headers: { 'Content-Type': 'application/json' },
|
|
1395
|
-
body: JSON.stringify(
|
|
1396
|
-
agentId,
|
|
1397
|
-
title: conv?.title || prompt.substring(0, 50),
|
|
1398
|
-
workingDirectory: conv?.workingDirectory || null
|
|
1399
|
-
})
|
|
1428
|
+
body: JSON.stringify(createBody)
|
|
1400
1429
|
});
|
|
1401
1430
|
if (!createResp.ok) throw new Error(`Failed to recreate conversation: HTTP ${createResp.status}`);
|
|
1402
1431
|
const { conversation: newConv } = await createResp.json();
|
|
@@ -1406,7 +1435,7 @@ class AgentGUIClient {
|
|
|
1406
1435
|
window.conversationManager.select(newConv.id);
|
|
1407
1436
|
}
|
|
1408
1437
|
this.updateUrlForConversation(newConv.id);
|
|
1409
|
-
return this.streamToConversation(newConv.id, prompt, agentId);
|
|
1438
|
+
return this.streamToConversation(newConv.id, prompt, agentId, model);
|
|
1410
1439
|
}
|
|
1411
1440
|
|
|
1412
1441
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
@@ -1698,6 +1727,9 @@ class AgentGUIClient {
|
|
|
1698
1727
|
}
|
|
1699
1728
|
|
|
1700
1729
|
window.dispatchEvent(new CustomEvent('agents-loaded', { detail: { agents } }));
|
|
1730
|
+
if (agents.length > 0 && !this._agentLocked) {
|
|
1731
|
+
this.loadModelsForAgent(agents[0].id);
|
|
1732
|
+
}
|
|
1701
1733
|
return agents;
|
|
1702
1734
|
} catch (error) {
|
|
1703
1735
|
console.error('Failed to load agents:', error);
|
|
@@ -1706,6 +1738,61 @@ class AgentGUIClient {
|
|
|
1706
1738
|
});
|
|
1707
1739
|
}
|
|
1708
1740
|
|
|
1741
|
+
async loadModelsForAgent(agentId) {
|
|
1742
|
+
if (!agentId || !this.ui.modelSelector) return;
|
|
1743
|
+
const cached = this._modelCache.get(agentId);
|
|
1744
|
+
if (cached) {
|
|
1745
|
+
this._populateModelSelector(cached);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
try {
|
|
1749
|
+
const response = await fetch(window.__BASE_URL + `/api/agents/${agentId}/models`);
|
|
1750
|
+
const { models } = await response.json();
|
|
1751
|
+
this._modelCache.set(agentId, models || []);
|
|
1752
|
+
this._populateModelSelector(models || []);
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
console.error('Failed to load models:', error);
|
|
1755
|
+
this._populateModelSelector([]);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
_populateModelSelector(models) {
|
|
1760
|
+
if (!this.ui.modelSelector) return;
|
|
1761
|
+
if (!models || models.length === 0) {
|
|
1762
|
+
this.ui.modelSelector.innerHTML = '';
|
|
1763
|
+
this.ui.modelSelector.setAttribute('data-empty', 'true');
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
this.ui.modelSelector.removeAttribute('data-empty');
|
|
1767
|
+
this.ui.modelSelector.innerHTML = models
|
|
1768
|
+
.map(m => `<option value="${m.id}">${this.escapeHtml(m.label)}</option>`)
|
|
1769
|
+
.join('');
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
lockAgentAndModel(agentId, model) {
|
|
1773
|
+
this._agentLocked = true;
|
|
1774
|
+
if (this.ui.agentSelector) {
|
|
1775
|
+
this.ui.agentSelector.value = agentId;
|
|
1776
|
+
this.ui.agentSelector.disabled = true;
|
|
1777
|
+
}
|
|
1778
|
+
this.loadModelsForAgent(agentId).then(() => {
|
|
1779
|
+
if (this.ui.modelSelector) {
|
|
1780
|
+
if (model) this.ui.modelSelector.value = model;
|
|
1781
|
+
this.ui.modelSelector.disabled = true;
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
unlockAgentAndModel() {
|
|
1787
|
+
this._agentLocked = false;
|
|
1788
|
+
if (this.ui.agentSelector) {
|
|
1789
|
+
this.ui.agentSelector.disabled = false;
|
|
1790
|
+
}
|
|
1791
|
+
if (this.ui.modelSelector) {
|
|
1792
|
+
this.ui.modelSelector.disabled = false;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1709
1796
|
/**
|
|
1710
1797
|
* Load conversations
|
|
1711
1798
|
*/
|
|
@@ -1840,9 +1927,11 @@ class AgentGUIClient {
|
|
|
1840
1927
|
async createNewConversation(workingDirectory, title) {
|
|
1841
1928
|
try {
|
|
1842
1929
|
const agentId = this.ui.agentSelector?.value || 'claude-code';
|
|
1930
|
+
const model = this.ui.modelSelector?.value || null;
|
|
1843
1931
|
const convTitle = title || 'New Conversation';
|
|
1844
1932
|
const body = { agentId, title: convTitle };
|
|
1845
1933
|
if (workingDirectory) body.workingDirectory = workingDirectory;
|
|
1934
|
+
if (model) body.model = model;
|
|
1846
1935
|
|
|
1847
1936
|
const response = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
1848
1937
|
method: 'POST',
|
|
@@ -1930,6 +2019,14 @@ class AgentGUIClient {
|
|
|
1930
2019
|
outputEl.appendChild(cached.dom.firstChild);
|
|
1931
2020
|
}
|
|
1932
2021
|
this.state.currentConversation = cached.conversation;
|
|
2022
|
+
const cachedHasActivity = cached.conversation.messageCount > 0 || this.state.streamingConversations.has(conversationId);
|
|
2023
|
+
if (cachedHasActivity) {
|
|
2024
|
+
this.lockAgentAndModel(cached.conversation.agentType || 'claude-code', cached.conversation.model || null);
|
|
2025
|
+
} else {
|
|
2026
|
+
this.unlockAgentAndModel();
|
|
2027
|
+
if (this.ui.agentSelector && cached.conversation.agentType) this.ui.agentSelector.value = cached.conversation.agentType;
|
|
2028
|
+
if (cached.conversation.agentType) this.loadModelsForAgent(cached.conversation.agentType);
|
|
2029
|
+
}
|
|
1933
2030
|
this.conversationCache.delete(conversationId);
|
|
1934
2031
|
this.restoreScrollPosition(conversationId);
|
|
1935
2032
|
this.enableControls();
|
|
@@ -1957,6 +2054,14 @@ class AgentGUIClient {
|
|
|
1957
2054
|
const { conversation, isActivelyStreaming, latestSession, chunks: rawChunks, totalChunks, messages: allMessages } = await resp.json();
|
|
1958
2055
|
|
|
1959
2056
|
this.state.currentConversation = conversation;
|
|
2057
|
+
const hasActivity = (allMessages && allMessages.length > 0) || isActivelyStreaming || latestSession || this.state.streamingConversations.has(conversationId);
|
|
2058
|
+
if (hasActivity) {
|
|
2059
|
+
this.lockAgentAndModel(conversation.agentType || 'claude-code', conversation.model || null);
|
|
2060
|
+
} else {
|
|
2061
|
+
this.unlockAgentAndModel();
|
|
2062
|
+
if (this.ui.agentSelector && conversation.agentType) this.ui.agentSelector.value = conversation.agentType;
|
|
2063
|
+
if (conversation.agentType) this.loadModelsForAgent(conversation.agentType);
|
|
2064
|
+
}
|
|
1960
2065
|
|
|
1961
2066
|
const chunks = (rawChunks || []).map(chunk => ({
|
|
1962
2067
|
...chunk,
|
|
@@ -1975,7 +2080,7 @@ class AgentGUIClient {
|
|
|
1975
2080
|
outputEl.innerHTML = `
|
|
1976
2081
|
<div class="conversation-header">
|
|
1977
2082
|
<h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
|
|
1978
|
-
<p class="text-secondary">${conversation.agentType || 'unknown'} - ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
|
|
2083
|
+
<p class="text-secondary">${conversation.agentType || 'unknown'}${conversation.model ? ' (' + this.escapeHtml(conversation.model) + ')' : ''} - ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
|
|
1979
2084
|
</div>
|
|
1980
2085
|
<div class="conversation-messages"></div>
|
|
1981
2086
|
`;
|
|
@@ -93,6 +93,7 @@ class ConversationManager {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async openFolderBrowser() {
|
|
96
|
+
window.dispatchEvent(new CustomEvent('preparing-new-conversation'));
|
|
96
97
|
if (!this.folderBrowser.modal) {
|
|
97
98
|
this.createNew();
|
|
98
99
|
return;
|
|
@@ -375,8 +376,9 @@ class ConversationManager {
|
|
|
375
376
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
376
377
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
377
378
|
const agent = conv.agentType || 'unknown';
|
|
379
|
+
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
378
380
|
const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
|
|
379
|
-
const metaParts = [agent, timestamp];
|
|
381
|
+
const metaParts = [agent + modelLabel, timestamp];
|
|
380
382
|
if (wd) metaParts.push(wd);
|
|
381
383
|
|
|
382
384
|
const titleEl = el.querySelector('.conversation-item-title');
|
|
@@ -402,8 +404,9 @@ class ConversationManager {
|
|
|
402
404
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
403
405
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
404
406
|
const agent = conv.agentType || 'unknown';
|
|
407
|
+
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
405
408
|
const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
|
|
406
|
-
const metaParts = [agent, timestamp];
|
|
409
|
+
const metaParts = [agent + modelLabel, timestamp];
|
|
407
410
|
if (wd) metaParts.push(wd);
|
|
408
411
|
|
|
409
412
|
const streamingBadge = isStreaming
|
|
@@ -466,6 +469,7 @@ class ConversationManager {
|
|
|
466
469
|
}
|
|
467
470
|
|
|
468
471
|
createNew() {
|
|
472
|
+
window.dispatchEvent(new CustomEvent('preparing-new-conversation'));
|
|
469
473
|
window.dispatchEvent(new CustomEvent('create-new-conversation'));
|
|
470
474
|
}
|
|
471
475
|
|