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 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
  },
@@ -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: () => ['--experimental-acp', '--yolo'],
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.220",
3
+ "version": "1.0.222",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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
@@ -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 agentId = this.ui.agentSelector?.value || 'claude-code';
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 (this.state.currentConversation?.id) {
1120
- await this.streamToConversation(this.state.currentConversation.id, savedPrompt, agentId);
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({ agentId, title: savedPrompt.substring(0, 50) })
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({ content: prompt, agentId })
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