groove-dev 0.27.144 → 0.27.146

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.
Files changed (135) hide show
  1. package/CLAUDE.md +7 -0
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +12 -6
  5. package/node_modules/@groove-dev/daemon/src/conversations.js +59 -58
  6. package/node_modules/@groove-dev/daemon/src/introducer.js +20 -0
  7. package/node_modules/@groove-dev/daemon/src/process.js +262 -15
  8. package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +1 -3
  9. package/node_modules/@groove-dev/daemon/src/rotator.js +15 -3
  10. package/node_modules/@groove-dev/daemon/src/routes/agents.js +49 -83
  11. package/node_modules/@groove-dev/daemon/templates/lab-general.json +12 -0
  12. package/node_modules/@groove-dev/daemon/templates/llama-cpp-setup.json +12 -0
  13. package/node_modules/@groove-dev/gui/dist/assets/index-BKbsE_hn.js +1011 -0
  14. package/node_modules/@groove-dev/gui/dist/assets/index-CEkPsSAm.css +1 -0
  15. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  16. package/node_modules/@groove-dev/gui/package.json +1 -1
  17. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +80 -95
  18. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
  19. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +132 -4
  20. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +3 -8
  21. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +199 -75
  22. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +21 -4
  23. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +10 -13
  24. package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +3 -3
  25. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +42 -34
  26. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +9 -3
  27. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +13 -3
  28. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +66 -65
  29. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
  30. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +124 -127
  31. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  32. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +2 -0
  33. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +24 -1
  34. package/node_modules/@groove-dev/gui/src/components/ui/question-modal.jsx +107 -0
  35. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +2 -2
  36. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
  37. package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
  38. package/node_modules/@groove-dev/gui/src/stores/groove.js +49 -2
  39. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +18 -2
  40. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +14 -14
  41. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +68 -32
  42. package/node_modules/@groove-dev/gui/src/views/models.jsx +57 -36
  43. package/node_modules/axios/CHANGELOG.md +260 -0
  44. package/node_modules/axios/README.md +595 -223
  45. package/node_modules/axios/dist/axios.js +1460 -1090
  46. package/node_modules/axios/dist/axios.js.map +1 -1
  47. package/node_modules/axios/dist/axios.min.js +3 -3
  48. package/node_modules/axios/dist/axios.min.js.map +1 -1
  49. package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
  50. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  51. package/node_modules/axios/dist/esm/axios.js +1557 -1128
  52. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  53. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  54. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  55. package/node_modules/axios/dist/node/axios.cjs +1594 -1057
  56. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  57. package/node_modules/axios/index.d.cts +40 -41
  58. package/node_modules/axios/index.d.ts +151 -227
  59. package/node_modules/axios/index.js +2 -0
  60. package/node_modules/axios/lib/adapters/adapters.js +4 -2
  61. package/node_modules/axios/lib/adapters/fetch.js +147 -16
  62. package/node_modules/axios/lib/adapters/http.js +306 -58
  63. package/node_modules/axios/lib/adapters/xhr.js +6 -2
  64. package/node_modules/axios/lib/core/Axios.js +7 -3
  65. package/node_modules/axios/lib/core/AxiosError.js +120 -34
  66. package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
  67. package/node_modules/axios/lib/core/buildFullPath.js +1 -1
  68. package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
  69. package/node_modules/axios/lib/core/mergeConfig.js +21 -4
  70. package/node_modules/axios/lib/core/settle.js +7 -11
  71. package/node_modules/axios/lib/defaults/index.js +14 -9
  72. package/node_modules/axios/lib/env/data.js +1 -1
  73. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
  74. package/node_modules/axios/lib/helpers/buildURL.js +1 -1
  75. package/node_modules/axios/lib/helpers/cookies.js +14 -2
  76. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
  77. package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
  78. package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
  79. package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
  80. package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
  81. package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
  82. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
  83. package/node_modules/axios/lib/helpers/toFormData.js +10 -2
  84. package/node_modules/axios/lib/helpers/validator.js +3 -1
  85. package/node_modules/axios/lib/utils.js +33 -21
  86. package/node_modules/axios/package.json +17 -24
  87. package/node_modules/follow-redirects/README.md +7 -5
  88. package/node_modules/follow-redirects/index.js +24 -1
  89. package/node_modules/follow-redirects/package.json +1 -1
  90. package/package.json +1 -1
  91. package/packages/cli/package.json +1 -1
  92. package/packages/daemon/package.json +1 -1
  93. package/packages/daemon/src/api.js +12 -6
  94. package/packages/daemon/src/conversations.js +59 -58
  95. package/packages/daemon/src/introducer.js +20 -0
  96. package/packages/daemon/src/process.js +262 -15
  97. package/packages/daemon/src/providers/groove-network.js +1 -3
  98. package/packages/daemon/src/rotator.js +15 -3
  99. package/packages/daemon/src/routes/agents.js +49 -83
  100. package/packages/daemon/templates/lab-general.json +12 -0
  101. package/packages/daemon/templates/llama-cpp-setup.json +12 -0
  102. package/packages/gui/dist/assets/index-BKbsE_hn.js +1011 -0
  103. package/packages/gui/dist/assets/index-CEkPsSAm.css +1 -0
  104. package/packages/gui/dist/index.html +2 -2
  105. package/packages/gui/package.json +1 -1
  106. package/packages/gui/src/components/agents/agent-feed.jsx +80 -95
  107. package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
  108. package/packages/gui/src/components/agents/spawn-wizard.jsx +132 -4
  109. package/packages/gui/src/components/chat/chat-header.jsx +3 -8
  110. package/packages/gui/src/components/chat/chat-input.jsx +199 -75
  111. package/packages/gui/src/components/chat/chat-messages.jsx +21 -4
  112. package/packages/gui/src/components/chat/chat-view.jsx +10 -13
  113. package/packages/gui/src/components/chat/model-picker.jsx +3 -3
  114. package/packages/gui/src/components/lab/chat-playground.jsx +42 -34
  115. package/packages/gui/src/components/lab/lab-assistant.jsx +9 -3
  116. package/packages/gui/src/components/lab/metrics-panel.jsx +13 -3
  117. package/packages/gui/src/components/lab/parameter-panel.jsx +66 -65
  118. package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
  119. package/packages/gui/src/components/lab/runtime-config.jsx +124 -127
  120. package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  121. package/packages/gui/src/components/layout/app-shell.jsx +2 -0
  122. package/packages/gui/src/components/layout/status-bar.jsx +24 -1
  123. package/packages/gui/src/components/ui/question-modal.jsx +107 -0
  124. package/packages/gui/src/components/ui/sheet.jsx +2 -2
  125. package/packages/gui/src/components/ui/slider.jsx +8 -8
  126. package/packages/gui/src/lib/status.js +1 -0
  127. package/packages/gui/src/stores/groove.js +49 -2
  128. package/packages/gui/src/stores/slices/agents-slice.js +18 -2
  129. package/packages/gui/src/stores/slices/chat-slice.js +14 -14
  130. package/packages/gui/src/views/model-lab.jsx +68 -32
  131. package/packages/gui/src/views/models.jsx +57 -36
  132. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +0 -1012
  133. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +0 -1
  134. package/packages/gui/dist/assets/index-BcoF6_eF.js +0 -1012
  135. package/packages/gui/dist/assets/index-Dd7qhiEd.css +0 -1
@@ -1,12 +1,12 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { useState, useRef, useEffect, useCallback } from 'react';
2
+ import { useState, useRef } from 'react';
3
3
  import { useGrooveStore } from '../../stores/groove';
4
4
  import { Badge } from '../ui/badge';
5
5
  import { AgentFeed } from './agent-feed';
6
6
  import { AgentConfig } from './agent-config';
7
7
  import { AgentTelemetry } from './agent-telemetry';
8
8
  import { AgentMdFiles } from './agent-mdfiles';
9
- import { MessageSquare, Settings, Activity, FileText, Pencil, Check, X, TrendingDown } from 'lucide-react';
9
+ import { MessageSquare, Settings, Activity, FileText, Pencil, Check, X } from 'lucide-react';
10
10
  import { fmtNum, fmtUptime } from '../../lib/format';
11
11
  import { cn } from '../../lib/cn';
12
12
  import { roleColor } from '../../lib/status';
@@ -77,56 +77,6 @@ function InlineName({ agent }) {
77
77
  );
78
78
  }
79
79
 
80
- function useRoutingSuggestion(agentId, isAlive) {
81
- const [suggestion, setSuggestion] = useState(null);
82
- const [dismissed, setDismissed] = useState(false);
83
-
84
- useEffect(() => {
85
- if (!agentId || !isAlive || dismissed) { setSuggestion(null); return; }
86
- let cancelled = false;
87
- async function poll() {
88
- try {
89
- const res = await fetch(`/api/agents/${agentId}/routing/suggestion`);
90
- if (cancelled) return;
91
- if (res.status === 204 || !res.ok) { setSuggestion(null); return; }
92
- const data = await res.json();
93
- setSuggestion(data);
94
- } catch { setSuggestion(null); }
95
- }
96
- poll();
97
- const id = setInterval(poll, 30000);
98
- return () => { cancelled = true; clearInterval(id); };
99
- }, [agentId, isAlive, dismissed]);
100
-
101
- const dismiss = useCallback(() => setDismissed(true), []);
102
- const reset = useCallback(() => setDismissed(false), []);
103
-
104
- return { suggestion: dismissed ? null : suggestion, dismiss, reset };
105
- }
106
-
107
- function DownshiftPill({ suggestion, onAccept, onDismiss }) {
108
- if (!suggestion) return null;
109
- const { suggestedModel } = suggestion;
110
- return (
111
- <div className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-success/10 border border-success/20 text-2xs font-mono animate-in fade-in slide-in-from-left-1 duration-200">
112
- <TrendingDown size={10} className="text-success flex-shrink-0" />
113
- <span className="text-success/90 truncate max-w-[80px]">{suggestedModel.name}</span>
114
- <button
115
- onClick={onAccept}
116
- className="px-1 py-px rounded bg-success/20 text-success font-semibold hover:bg-success/30 transition-colors cursor-pointer"
117
- >
118
- Switch
119
- </button>
120
- <button
121
- onClick={onDismiss}
122
- className="p-0.5 text-text-4 hover:text-text-1 cursor-pointer"
123
- >
124
- <X size={8} />
125
- </button>
126
- </div>
127
- );
128
- }
129
-
130
80
  export function AgentPanel() {
131
81
  const detailPanel = useGrooveStore((s) => s.detailPanel);
132
82
  const agents = useGrooveStore((s) => s.agents);
@@ -141,7 +91,6 @@ export function AgentPanel() {
141
91
  else if (cachedAgentRef.current && cachedAgentRef.current.id !== agentId) cachedAgentRef.current = null;
142
92
  const agent = liveAgent || cachedAgentRef.current;
143
93
  const isAlive = liveAgent?.status === 'running' || liveAgent?.status === 'starting';
144
- const { suggestion, dismiss: dismissSuggestion } = useRoutingSuggestion(agentId, isAlive);
145
94
 
146
95
  if (!agent) return null;
147
96
  if (activeTeamId && agent.teamId && agent.teamId !== activeTeamId) return null;
@@ -151,17 +100,6 @@ export function AgentPanel() {
151
100
  const uptime = spawned ? Math.floor((Date.now() - new Date(spawned).getTime()) / 1000) : 0;
152
101
  const colors = roleColor(agent.role);
153
102
 
154
- async function acceptSuggestion() {
155
- if (!suggestion) return;
156
- try {
157
- await api.patch(`/agents/${agent.id}`, { model: suggestion.suggestedModel.id });
158
- addToast('success', `Model → ${suggestion.suggestedModel.name}`);
159
- dismissSuggestion();
160
- } catch (err) {
161
- addToast('error', 'Model switch failed', err.message);
162
- }
163
- }
164
-
165
103
  return (
166
104
  <div className="flex flex-col h-full">
167
105
  {/* ── Header ─────────────────────────────────────────── */}
@@ -194,12 +132,6 @@ export function AgentPanel() {
194
132
  )}
195
133
  <span className="text-text-4">·</span>
196
134
  <span>{fmtUptime(uptime)}</span>
197
- {suggestion && (
198
- <>
199
- <span className="text-text-4">·</span>
200
- <DownshiftPill suggestion={suggestion} onAccept={acceptSuggestion} onDismiss={dismissSuggestion} />
201
- </>
202
- )}
203
135
  </div>
204
136
  </div>
205
137
 
@@ -12,7 +12,7 @@ import {
12
12
  Shield, Database, Megaphone, Calculator, UserCheck,
13
13
  Headphones, BarChart3, Rocket, ChevronDown, Pen, Presentation,
14
14
  Sparkles, X, Search, AlertTriangle, Plug, MessageCircle, GitBranch, Globe,
15
- Check,
15
+ Check, Users, Plus,
16
16
  } from 'lucide-react';
17
17
  import { api } from '../../lib/api';
18
18
  import { Dialog, DialogContent } from '../ui/dialog';
@@ -53,6 +53,8 @@ export function SpawnWizard() {
53
53
  const closeDetail = useGrooveStore((s) => s.closeDetail);
54
54
  const spawnAgent = useGrooveStore((s) => s.spawnAgent);
55
55
  const fetchProviders = useGrooveStore((s) => s.fetchProviders);
56
+ const teams = useGrooveStore((s) => s.teams);
57
+ const createTeam = useGrooveStore((s) => s.createTeam);
56
58
 
57
59
  const open = detailPanel?.type === 'spawn';
58
60
  const [role, setRole] = useState('');
@@ -80,6 +82,9 @@ export function SpawnWizard() {
80
82
  const [preflightDialog, setPreflightDialog] = useState(null);
81
83
  const [ollamaInstalled, setOllamaInstalled] = useState([]);
82
84
  const [ollamaServerRunning, setOllamaServerRunning] = useState(false);
85
+ const [teamMode, setTeamMode] = useState('new');
86
+ const [newTeamName, setNewTeamName] = useState('');
87
+ const [selectedTeamId, setSelectedTeamId] = useState('');
83
88
  const federation = useGrooveStore((s) => s.federation);
84
89
  const ollamaRunningModels = useGrooveStore((s) => s.ollamaRunningModels);
85
90
 
@@ -102,6 +107,25 @@ export function SpawnWizard() {
102
107
  const best = priority.find((pid) => installed.some((p) => p.id === pid)) || installed[0].id;
103
108
  setProvider(best);
104
109
  }
110
+ if (_presetProvider) setProvider(_presetProvider);
111
+ if (_presetModel) {
112
+ const prov = list.find((p) => p.id === (_presetProvider || provider));
113
+ const models = prov?.models || [];
114
+ const exact = models.find((m) => m.id === _presetModel);
115
+ if (exact) {
116
+ setModel(_presetModel);
117
+ } else {
118
+ const parts = _presetModel.split(':');
119
+ const modelName = parts.length >= 3 ? parts.slice(2).join(':') : parts[parts.length - 1];
120
+ const match = models.find((m) =>
121
+ m.id === _presetModel ||
122
+ m.id === `gguf:${modelName}` ||
123
+ m.id.endsWith(`:${modelName}`) ||
124
+ m.name === modelName
125
+ );
126
+ setModel(match?.id || _presetModel);
127
+ }
128
+ }
105
129
  }).catch(() => {});
106
130
  api.get('/integrations/installed').then((data) => {
107
131
  setInstalledIntegrations(Array.isArray(data) ? data : []);
@@ -114,6 +138,10 @@ export function SpawnWizard() {
114
138
  }).catch(() => {});
115
139
  setRole(''); setCustomRole(''); setName('');
116
140
  setProvider(_presetProvider); setModel(_presetModel);
141
+ setTeamMode('new'); setSelectedTeamId('');
142
+ setNewTeamName(_presetModel
143
+ ? _presetModel.split(':').pop().replace(/[-_]/g, ' ')
144
+ : '');
117
145
  setPrompt('');
118
146
  setSelectedIntegrations([]);
119
147
  setIntegrationApproval('manual');
@@ -153,6 +181,14 @@ export function SpawnWizard() {
153
181
  async function runSpawn() {
154
182
  setSpawning(true);
155
183
  try {
184
+ let teamId;
185
+ if (teamMode === 'new') {
186
+ const teamName = newTeamName.trim() || selectedRole || 'New Team';
187
+ const team = await createTeam(teamName);
188
+ teamId = team.id;
189
+ } else if (teamMode === 'existing' && selectedTeamId) {
190
+ teamId = selectedTeamId;
191
+ }
156
192
  const config = {
157
193
  role: selectedRole,
158
194
  ...(name && { name: name.replace(/\s+/g, '-') }),
@@ -164,6 +200,7 @@ export function SpawnWizard() {
164
200
  ...(selectedRepos.length > 0 && { repos: selectedRepos }),
165
201
  ...(selectedPersonality && { personality: selectedPersonality }),
166
202
  ...(selectedRole === 'ambassador' && selectedPeerId && { peerId: selectedPeerId }),
203
+ ...(teamId && { teamId }),
167
204
  };
168
205
  await spawnAgent(config);
169
206
  closeDetail();
@@ -415,7 +452,36 @@ export function SpawnWizard() {
415
452
  }
416
453
  </optgroup>
417
454
  </>
418
- ) : (
455
+ ) : provider === 'local' ? (() => {
456
+ const runtimeModels = availableModels.filter((m) => m.source === 'runtime');
457
+ const ggufModels = availableModels.filter((m) => m.source === 'gguf');
458
+ const ollamaMs = availableModels.filter((m) => m.source === 'ollama' || !m.source);
459
+ return (
460
+ <>
461
+ {runtimeModels.length > 0 && (
462
+ <optgroup label="Lab Runtimes">
463
+ {runtimeModels.map((m) => (
464
+ <option key={m.id} value={m.id}>{m.name}{m.runtimeType ? ` (${m.runtimeType})` : ''}</option>
465
+ ))}
466
+ </optgroup>
467
+ )}
468
+ {ggufModels.length > 0 && (
469
+ <optgroup label="GGUF Models">
470
+ {ggufModels.map((m) => (
471
+ <option key={m.id} value={m.id}>{m.name}{m.hasRuntime ? '' : ' (no runtime)'}</option>
472
+ ))}
473
+ </optgroup>
474
+ )}
475
+ {ollamaMs.length > 0 && (
476
+ <optgroup label="Ollama">
477
+ {ollamaMs.map((m) => (
478
+ <option key={m.id} value={m.id}>{m.name}</option>
479
+ ))}
480
+ </optgroup>
481
+ )}
482
+ </>
483
+ );
484
+ })() : (
419
485
  availableModels.map((m) => (
420
486
  <option key={m.id} value={m.id}>{m.name}</option>
421
487
  ))
@@ -459,6 +525,64 @@ export function SpawnWizard() {
459
525
  </div>
460
526
  )}
461
527
 
528
+ {/* Team */}
529
+ <div className="space-y-1.5">
530
+ <label className="text-xs font-medium text-text-2 font-sans">Team</label>
531
+ <div className="flex gap-1.5">
532
+ <button
533
+ onClick={() => setTeamMode('new')}
534
+ className={cn(
535
+ 'flex-1 flex items-center gap-2 px-3 py-2 rounded-md border text-left transition-all cursor-pointer',
536
+ teamMode === 'new'
537
+ ? 'border-accent bg-accent/5'
538
+ : 'border-border-subtle bg-surface-1 hover:border-border',
539
+ )}
540
+ >
541
+ <Plus size={13} className={teamMode === 'new' ? 'text-accent' : 'text-text-3'} />
542
+ <div>
543
+ <div className="text-2xs font-semibold text-text-0 font-sans">New Team</div>
544
+ </div>
545
+ </button>
546
+ <button
547
+ onClick={() => setTeamMode('existing')}
548
+ className={cn(
549
+ 'flex-1 flex items-center gap-2 px-3 py-2 rounded-md border text-left transition-all cursor-pointer',
550
+ teamMode === 'existing'
551
+ ? 'border-accent bg-accent/5'
552
+ : 'border-border-subtle bg-surface-1 hover:border-border',
553
+ )}
554
+ >
555
+ <Users size={13} className={teamMode === 'existing' ? 'text-accent' : 'text-text-3'} />
556
+ <div>
557
+ <div className="text-2xs font-semibold text-text-0 font-sans">Existing</div>
558
+ </div>
559
+ </button>
560
+ </div>
561
+ {teamMode === 'new' && (
562
+ <Input
563
+ value={newTeamName}
564
+ onChange={(e) => setNewTeamName(e.target.value)}
565
+ placeholder="Team name..."
566
+ className="text-xs"
567
+ />
568
+ )}
569
+ {teamMode === 'existing' && (
570
+ <div className="relative">
571
+ <select
572
+ value={selectedTeamId}
573
+ onChange={(e) => setSelectedTeamId(e.target.value)}
574
+ className="w-full h-8 px-3 pr-8 text-sm rounded-md bg-surface-1 border border-border text-text-0 font-sans appearance-none cursor-pointer focus:outline-none focus:ring-1 focus:ring-accent"
575
+ >
576
+ <option value="">Select team...</option>
577
+ {teams.filter((t) => !t.archived).map((t) => (
578
+ <option key={t.id} value={t.id}>{t.name}</option>
579
+ ))}
580
+ </select>
581
+ <ChevronDown size={14} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-3 pointer-events-none" />
582
+ </div>
583
+ )}
584
+ </div>
585
+
462
586
  {/* Integrations */}
463
587
  <div className="space-y-1.5">
464
588
  <label className="text-xs font-medium text-text-2 font-sans">Integrations</label>
@@ -756,18 +880,22 @@ export function SpawnWizard() {
756
880
  </div>
757
881
  )}
758
882
  {selectedRole && installedProviders.length > 0 && (
759
- <div className="flex items-center gap-2 mb-3 text-xs text-text-3 font-sans">
883
+ <div className="flex items-center gap-2 mb-3 text-xs text-text-3 font-sans flex-wrap">
760
884
  <span>Spawning</span>
761
885
  <Badge variant="accent">{selectedRole}</Badge>
762
886
  {provider && <span>on {selectedProvider?.name || provider}</span>}
763
887
  {name && <span>as {name.replace(/\s+/g, '-')}</span>}
888
+ {teamMode === 'new' && <span>in new team{newTeamName ? ` "${newTeamName}"` : ''}</span>}
889
+ {teamMode === 'existing' && selectedTeamId && (
890
+ <span>in {teams.find((t) => t.id === selectedTeamId)?.name || 'team'}</span>
891
+ )}
764
892
  </div>
765
893
  )}
766
894
  <Button
767
895
  variant="primary"
768
896
  size="lg"
769
897
  onClick={handleSpawn}
770
- disabled={!selectedRole || spawning || installedProviders.length === 0}
898
+ disabled={!selectedRole || spawning || installedProviders.length === 0 || (teamMode === 'existing' && !selectedTeamId)}
771
899
  className="w-full"
772
900
  >
773
901
  {spawning ? 'Spawning...' : 'Spawn Agent'}
@@ -3,11 +3,12 @@ import { useState, useRef, useEffect } from 'react';
3
3
  import { Pencil, Pin, PinOff, Trash2, Hash, MoreHorizontal, ChevronDown, X } from 'lucide-react';
4
4
  import { useGrooveStore } from '../../stores/groove';
5
5
  import { fmtNum } from '../../lib/format';
6
- import { ModelPicker, formatModelName } from './model-picker';
7
6
  import { cn } from '../../lib/cn';
8
7
  import { roleColor } from '../../lib/status';
9
8
 
10
9
  const CHAT_ROLES = [
10
+ { id: 'chat', label: 'Chat', desc: 'General conversation' },
11
+ { id: 'research', label: 'Research Assistant', desc: 'Explore ideas, web search' },
11
12
  { id: 'fullstack', label: 'Fullstack', desc: 'End-to-end engineering' },
12
13
  { id: 'backend', label: 'Backend', desc: 'APIs, services, databases' },
13
14
  { id: 'frontend', label: 'Frontend', desc: 'UI, components, styling' },
@@ -24,7 +25,7 @@ const CHAT_ROLES = [
24
25
  { id: 'support', label: 'Support', desc: 'Customer support, FAQs' },
25
26
  ];
26
27
 
27
- export function ChatHeader({ conversation, model, onModelChange, role, onRoleChange, sidebarCollapsed }) {
28
+ export function ChatHeader({ conversation, role, onRoleChange, sidebarCollapsed }) {
28
29
  const renameConversation = useGrooveStore((s) => s.renameConversation);
29
30
  const pinConversation = useGrooveStore((s) => s.pinConversation);
30
31
  const deleteConversation = useGrooveStore((s) => s.deleteConversation);
@@ -155,12 +156,6 @@ export function ChatHeader({ conversation, model, onModelChange, role, onRoleCha
155
156
  )}
156
157
  </div>
157
158
 
158
- <ModelPicker
159
- value={model || { provider: conversation.provider, model: conversation.model }}
160
- onChange={onModelChange}
161
- disabled={false}
162
- />
163
-
164
159
  {tokens > 0 && (
165
160
  <span className="text-2xs text-text-3 font-mono tabular-nums">{fmtNum(tokens)} tok</span>
166
161
  )}