groove-dev 0.26.38 → 0.27.0

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 (171) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/CLAUDE.md +24 -19
  3. package/node_modules/@groove-dev/cli/bin/groove.js +2 -0
  4. package/node_modules/@groove-dev/cli/package.json +1 -1
  5. package/node_modules/@groove-dev/cli/src/commands/nuke.js +16 -4
  6. package/node_modules/@groove-dev/cli/src/commands/stop.js +17 -2
  7. package/node_modules/@groove-dev/daemon/integrations-registry.json +681 -75
  8. package/node_modules/@groove-dev/daemon/package.json +1 -1
  9. package/node_modules/@groove-dev/daemon/src/adaptive.js +23 -25
  10. package/node_modules/@groove-dev/daemon/src/api.js +346 -22
  11. package/node_modules/@groove-dev/daemon/src/classifier.js +53 -6
  12. package/node_modules/@groove-dev/daemon/src/firstrun.js +14 -1
  13. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +2 -2
  14. package/node_modules/@groove-dev/daemon/src/index.js +28 -4
  15. package/node_modules/@groove-dev/daemon/src/integrations.js +215 -14
  16. package/node_modules/@groove-dev/daemon/src/introducer.js +84 -11
  17. package/node_modules/@groove-dev/daemon/src/journalist.js +43 -1
  18. package/node_modules/@groove-dev/daemon/src/lockmanager.js +60 -0
  19. package/node_modules/@groove-dev/daemon/src/mcp-manager.js +270 -0
  20. package/node_modules/@groove-dev/daemon/src/memory.js +370 -0
  21. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  22. package/node_modules/@groove-dev/daemon/src/process.js +141 -9
  23. package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
  24. package/node_modules/@groove-dev/daemon/src/rotator.js +334 -31
  25. package/node_modules/@groove-dev/daemon/src/router.js +43 -0
  26. package/node_modules/@groove-dev/daemon/src/tokentracker.js +70 -18
  27. package/node_modules/@groove-dev/daemon/src/validate.js +5 -13
  28. package/node_modules/@groove-dev/daemon/templates/groove-slides.cjs +306 -0
  29. package/node_modules/@groove-dev/daemon/test/classifier.test.js +3 -5
  30. package/node_modules/@groove-dev/daemon/test/lockmanager.test.js +64 -0
  31. package/node_modules/@groove-dev/daemon/test/memory.test.js +252 -0
  32. package/node_modules/@groove-dev/daemon/test/rotator.test.js +108 -0
  33. package/node_modules/@groove-dev/daemon/test/router.test.js +64 -0
  34. package/node_modules/@groove-dev/daemon/test/slides-engine.test.js +230 -0
  35. package/node_modules/@groove-dev/daemon/test/tokentracker.test.js +78 -0
  36. package/node_modules/@groove-dev/gui/dist/assets/index-DjORRpF0.css +1 -0
  37. package/node_modules/@groove-dev/gui/dist/assets/index-eCrVowF0.js +652 -0
  38. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  39. package/node_modules/@groove-dev/gui/package.json +1 -4
  40. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +26 -17
  41. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +22 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +53 -21
  43. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +132 -90
  44. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +212 -1
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +6 -2
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +495 -174
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +12 -2
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +55 -0
  49. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +3 -3
  50. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +24 -19
  51. package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +2 -2
  52. package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +391 -61
  53. package/node_modules/@groove-dev/gui/src/components/marketplace/marketplace-card.jsx +29 -7
  54. package/node_modules/@groove-dev/gui/src/lib/format.js +0 -6
  55. package/node_modules/@groove-dev/gui/src/lib/hooks/use-dashboard.js +23 -5
  56. package/node_modules/@groove-dev/gui/src/stores/groove.js +59 -9
  57. package/node_modules/@groove-dev/gui/src/views/agents.jsx +84 -10
  58. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +24 -21
  59. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +153 -85
  60. package/package.json +2 -8
  61. package/packages/cli/bin/groove.js +2 -0
  62. package/packages/cli/package.json +1 -1
  63. package/packages/cli/src/commands/nuke.js +16 -4
  64. package/packages/cli/src/commands/stop.js +17 -2
  65. package/packages/daemon/integrations-registry.json +681 -75
  66. package/packages/daemon/package.json +1 -1
  67. package/packages/daemon/src/adaptive.js +23 -25
  68. package/packages/daemon/src/api.js +346 -22
  69. package/packages/daemon/src/classifier.js +53 -6
  70. package/packages/daemon/src/firstrun.js +14 -1
  71. package/packages/daemon/src/gateways/manager.js +2 -2
  72. package/packages/daemon/src/index.js +28 -4
  73. package/packages/daemon/src/integrations.js +215 -14
  74. package/packages/daemon/src/introducer.js +84 -11
  75. package/packages/daemon/src/journalist.js +43 -1
  76. package/packages/daemon/src/lockmanager.js +60 -0
  77. package/packages/daemon/src/mcp-manager.js +270 -0
  78. package/packages/daemon/src/memory.js +370 -0
  79. package/packages/daemon/src/pm.js +1 -1
  80. package/packages/daemon/src/process.js +141 -9
  81. package/packages/daemon/src/registry.js +1 -1
  82. package/packages/daemon/src/rotator.js +334 -31
  83. package/packages/daemon/src/router.js +43 -0
  84. package/packages/daemon/src/tokentracker.js +70 -18
  85. package/packages/daemon/src/validate.js +5 -13
  86. package/packages/daemon/templates/groove-slides.cjs +306 -0
  87. package/packages/gui/dist/assets/index-DjORRpF0.css +1 -0
  88. package/packages/gui/dist/assets/index-eCrVowF0.js +652 -0
  89. package/packages/gui/dist/index.html +2 -2
  90. package/packages/gui/package.json +1 -4
  91. package/packages/gui/src/components/agents/agent-chat.jsx +26 -17
  92. package/packages/gui/src/components/agents/agent-config.jsx +22 -1
  93. package/packages/gui/src/components/agents/agent-feed.jsx +53 -21
  94. package/packages/gui/src/components/agents/agent-node.jsx +132 -90
  95. package/packages/gui/src/components/agents/spawn-wizard.jsx +212 -1
  96. package/packages/gui/src/components/dashboard/cache-ring.jsx +6 -2
  97. package/packages/gui/src/components/dashboard/intel-panel.jsx +495 -174
  98. package/packages/gui/src/components/dashboard/kpi-card.jsx +12 -2
  99. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +55 -0
  100. package/packages/gui/src/components/layout/activity-bar.jsx +3 -3
  101. package/packages/gui/src/components/layout/app-shell.jsx +24 -19
  102. package/packages/gui/src/components/layout/command-palette.jsx +2 -2
  103. package/packages/gui/src/components/marketplace/integration-wizard.jsx +391 -61
  104. package/packages/gui/src/components/marketplace/marketplace-card.jsx +29 -7
  105. package/packages/gui/src/lib/format.js +0 -6
  106. package/packages/gui/src/lib/hooks/use-dashboard.js +23 -5
  107. package/packages/gui/src/stores/groove.js +59 -9
  108. package/packages/gui/src/views/agents.jsx +84 -10
  109. package/packages/gui/src/views/dashboard.jsx +24 -21
  110. package/packages/gui/src/views/marketplace.jsx +153 -85
  111. package/node_modules/@groove-dev/gui/dist/assets/index-CEFKgLGB.css +0 -1
  112. package/node_modules/@groove-dev/gui/dist/assets/index-CaKBNWcK.js +0 -638
  113. package/node_modules/@groove-dev/gui/dist/groove-logo-short.png +0 -0
  114. package/node_modules/@groove-dev/gui/dist/groove-logo.png +0 -0
  115. package/node_modules/@groove-dev/gui/public/groove-logo-short.png +0 -0
  116. package/node_modules/@groove-dev/gui/public/groove-logo.png +0 -0
  117. package/node_modules/@groove-dev/gui/src/components/ui/dropdown-menu.jsx +0 -60
  118. package/node_modules/@groove-dev/gui/src/lib/hooks/use-media-query.js +0 -18
  119. package/node_modules/@radix-ui/react-dropdown-menu/LICENSE +0 -21
  120. package/node_modules/@radix-ui/react-dropdown-menu/README.md +0 -3
  121. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.mts +0 -97
  122. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.ts +0 -97
  123. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.js +0 -337
  124. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.js.map +0 -7
  125. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs +0 -305
  126. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs.map +0 -7
  127. package/node_modules/@radix-ui/react-dropdown-menu/package.json +0 -75
  128. package/node_modules/@radix-ui/react-popover/LICENSE +0 -21
  129. package/node_modules/@radix-ui/react-popover/README.md +0 -3
  130. package/node_modules/@radix-ui/react-popover/dist/index.d.mts +0 -85
  131. package/node_modules/@radix-ui/react-popover/dist/index.d.ts +0 -85
  132. package/node_modules/@radix-ui/react-popover/dist/index.js +0 -352
  133. package/node_modules/@radix-ui/react-popover/dist/index.js.map +0 -7
  134. package/node_modules/@radix-ui/react-popover/dist/index.mjs +0 -320
  135. package/node_modules/@radix-ui/react-popover/dist/index.mjs.map +0 -7
  136. package/node_modules/@radix-ui/react-popover/package.json +0 -82
  137. package/node_modules/@radix-ui/react-separator/LICENSE +0 -21
  138. package/node_modules/@radix-ui/react-separator/README.md +0 -3
  139. package/node_modules/@radix-ui/react-separator/dist/index.d.mts +0 -21
  140. package/node_modules/@radix-ui/react-separator/dist/index.d.ts +0 -21
  141. package/node_modules/@radix-ui/react-separator/dist/index.js +0 -65
  142. package/node_modules/@radix-ui/react-separator/dist/index.js.map +0 -7
  143. package/node_modules/@radix-ui/react-separator/dist/index.mjs +0 -32
  144. package/node_modules/@radix-ui/react-separator/dist/index.mjs.map +0 -7
  145. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/LICENSE +0 -21
  146. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/README.md +0 -3
  147. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.d.mts +0 -52
  148. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.d.ts +0 -52
  149. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.js +0 -80
  150. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.js.map +0 -7
  151. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.mjs +0 -47
  152. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.mjs.map +0 -7
  153. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/package.json +0 -69
  154. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/LICENSE +0 -21
  155. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/README.md +0 -3
  156. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.d.mts +0 -22
  157. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.d.ts +0 -22
  158. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.js +0 -152
  159. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.js.map +0 -7
  160. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.mjs +0 -119
  161. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.mjs.map +0 -7
  162. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/package.json +0 -64
  163. package/node_modules/@radix-ui/react-separator/package.json +0 -69
  164. package/packages/gui/dist/assets/index-CEFKgLGB.css +0 -1
  165. package/packages/gui/dist/assets/index-CaKBNWcK.js +0 -638
  166. package/packages/gui/dist/groove-logo-short.png +0 -0
  167. package/packages/gui/dist/groove-logo.png +0 -0
  168. package/packages/gui/public/groove-logo-short.png +0 -0
  169. package/packages/gui/public/groove-logo.png +0 -0
  170. package/packages/gui/src/components/ui/dropdown-menu.jsx +0 -60
  171. package/packages/gui/src/lib/hooks/use-media-query.js +0 -18
@@ -11,11 +11,47 @@ import {
11
11
  Server, Monitor, Code2, TestTube, Cloud, FileText,
12
12
  Shield, Database, Megaphone, Calculator, UserCheck,
13
13
  Headphones, BarChart3, Rocket, ChevronDown, Pen, Presentation,
14
- Sparkles, X, Search, AlertTriangle,
14
+ Sparkles, X, Search, AlertTriangle, Plug,
15
15
  } from 'lucide-react';
16
16
  import { api } from '../../lib/api';
17
17
  import { Dialog, DialogContent } from '../ui/dialog';
18
18
 
19
+ const INTEGRATION_LOGOS = {
20
+ 'google-workspace': 'https://cdn.simpleicons.org/google/white',
21
+ github: 'https://cdn.simpleicons.org/github/white',
22
+ stripe: 'https://cdn.simpleicons.org/stripe/635BFF',
23
+ gmail: 'https://cdn.simpleicons.org/gmail/EA4335',
24
+ 'google-calendar': 'https://cdn.simpleicons.org/googlecalendar/4285F4',
25
+ 'google-drive': 'https://cdn.simpleicons.org/googledrive/4285F4',
26
+ 'google-docs': 'https://cdn.simpleicons.org/googledocs/4285F4',
27
+ 'google-sheets': 'https://cdn.simpleicons.org/googlesheets/34A853',
28
+ 'google-slides': 'https://cdn.simpleicons.org/googleslides/FBBC04',
29
+ 'google-maps': 'https://cdn.simpleicons.org/googlemaps/4285F4',
30
+ postgres: 'https://cdn.simpleicons.org/postgresql/4169E1',
31
+ notion: 'https://cdn.simpleicons.org/notion/white',
32
+ linear: 'https://cdn.simpleicons.org/linear/5E6AD2',
33
+ 'brave-search': 'https://cdn.simpleicons.org/brave/FB542B',
34
+ 'home-assistant': 'https://cdn.simpleicons.org/homeassistant/18BCF2',
35
+ sentry: 'https://cdn.simpleicons.org/sentry/362D59',
36
+ elevenlabs: 'https://cdn.simpleicons.org/elevenlabs/white',
37
+ hubspot: 'https://cdn.simpleicons.org/hubspot/FF7A59',
38
+ jira: 'https://cdn.simpleicons.org/jira/0052CC',
39
+ sendgrid: 'https://cdn.simpleicons.org/sendgrid/1A82E2',
40
+ resend: 'https://cdn.simpleicons.org/resend/white',
41
+ replicate: 'https://cdn.simpleicons.org/replicate/white',
42
+ vercel: 'https://cdn.simpleicons.org/vercel/white',
43
+ supabase: 'https://cdn.simpleicons.org/supabase/3FCF8E',
44
+ mixpanel: 'https://cdn.simpleicons.org/mixpanel/7856FF',
45
+ datadog: 'https://cdn.simpleicons.org/datadog/632CA6',
46
+ airtable: 'https://cdn.simpleicons.org/airtable/18BFFF',
47
+ zendesk: 'https://cdn.simpleicons.org/zendesk/03363D',
48
+ intercom: 'https://cdn.simpleicons.org/intercom/6AFDEF',
49
+ twilio: 'https://cdn.simpleicons.org/twilio/F22F46',
50
+ telnyx: 'https://cdn.simpleicons.org/telnyx/00C08B',
51
+ aws: 'https://cdn.simpleicons.org/amazonaws/FF9900',
52
+ plaid: 'https://cdn.simpleicons.org/plaid/white',
53
+ };
54
+
19
55
  const ROLE_PRESETS = [
20
56
  { id: 'planner', label: 'Planner', desc: 'Plans the team and tasks', icon: Rocket, tier: 'Heavy' },
21
57
  { id: 'backend', label: 'Backend', desc: 'APIs, services, databases', icon: Server, tier: 'Medium' },
@@ -62,6 +98,11 @@ export function SpawnWizard() {
62
98
  const [selectedSkills, setSelectedSkills] = useState([]);
63
99
  const [skillModalOpen, setSkillModalOpen] = useState(false);
64
100
  const [skillSearch, setSkillSearch] = useState('');
101
+ const [installedIntegrations, setInstalledIntegrations] = useState([]);
102
+ const [selectedIntegrations, setSelectedIntegrations] = useState([]);
103
+ const [integrationModalOpen, setIntegrationModalOpen] = useState(false);
104
+ const [integrationSearch, setIntegrationSearch] = useState('');
105
+ const [integrationApproval, setIntegrationApproval] = useState('manual');
65
106
  const [spawning, setSpawning] = useState(false);
66
107
 
67
108
  useEffect(() => {
@@ -80,8 +121,13 @@ export function SpawnWizard() {
80
121
  api.get('/skills/installed').then((data) => {
81
122
  setInstalledSkills(Array.isArray(data) ? data : []);
82
123
  }).catch(() => {});
124
+ api.get('/integrations/installed').then((data) => {
125
+ setInstalledIntegrations(Array.isArray(data) ? data : []);
126
+ }).catch(() => {});
83
127
  setRole(''); setCustomRole(''); setName(''); setProvider(''); setModel(''); setPrompt('');
84
128
  setSelectedSkills([]);
129
+ setSelectedIntegrations([]);
130
+ setIntegrationApproval('manual');
85
131
  }
86
132
  }, [open, fetchProviders]);
87
133
 
@@ -101,6 +147,8 @@ export function SpawnWizard() {
101
147
  ...(model && { model }),
102
148
  ...(prompt && { prompt }),
103
149
  ...(selectedSkills.length > 0 && { skills: selectedSkills }),
150
+ ...(selectedIntegrations.length > 0 && { integrations: selectedIntegrations }),
151
+ ...(selectedIntegrations.length > 0 && { integrationApproval }),
104
152
  };
105
153
  await spawnAgent(config);
106
154
  closeDetail();
@@ -328,6 +376,169 @@ export function SpawnWizard() {
328
376
  </DialogContent>
329
377
  </Dialog>
330
378
 
379
+ {/* Integrations */}
380
+ <div className="space-y-1.5">
381
+ <label className="text-xs font-medium text-text-2 font-sans">Integrations</label>
382
+ <div className="flex flex-wrap items-center gap-1.5">
383
+ {selectedIntegrations.map((integrationId) => {
384
+ const integration = installedIntegrations.find((i) => i.id === integrationId);
385
+ const logoUrl = INTEGRATION_LOGOS[integrationId];
386
+ return (
387
+ <span
388
+ key={integrationId}
389
+ className="inline-flex items-center gap-1 px-2 py-1 rounded bg-accent/12 text-accent border border-accent/25 text-2xs font-sans"
390
+ >
391
+ {logoUrl ? (
392
+ <img src={logoUrl} alt="" className="w-2.5 h-2.5" />
393
+ ) : (
394
+ <Plug size={9} />
395
+ )}
396
+ {integration?.name || integrationId}
397
+ <button
398
+ onClick={() => setSelectedIntegrations((prev) => prev.filter((i) => i !== integrationId))}
399
+ className="ml-0.5 hover:text-text-0 cursor-pointer"
400
+ >
401
+ <X size={9} />
402
+ </button>
403
+ </span>
404
+ );
405
+ })}
406
+ <button
407
+ onClick={() => { setIntegrationModalOpen(true); setIntegrationSearch(''); }}
408
+ className={cn(
409
+ 'inline-flex items-center gap-1.5 px-2.5 py-1 rounded text-2xs font-sans transition-colors cursor-pointer',
410
+ 'bg-surface-0 text-text-2 border border-border-subtle hover:border-border hover:text-text-0',
411
+ )}
412
+ >
413
+ <Plug size={10} />
414
+ {selectedIntegrations.length > 0 ? 'Add integration' : 'Attach integration'}
415
+ </button>
416
+ </div>
417
+ </div>
418
+
419
+ {/* Integration picker modal */}
420
+ <Dialog open={integrationModalOpen} onOpenChange={setIntegrationModalOpen}>
421
+ <DialogContent title="Select Integration" className="max-w-sm">
422
+ <div className="space-y-3 p-4">
423
+ <div className="relative">
424
+ <Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text-4" />
425
+ <input
426
+ value={integrationSearch}
427
+ onChange={(e) => setIntegrationSearch(e.target.value)}
428
+ placeholder="Search integrations..."
429
+ autoFocus
430
+ className="w-full h-8 pl-8 pr-3 text-xs rounded-md bg-surface-0 border border-border text-text-0 font-sans focus:outline-none focus:ring-1 focus:ring-accent"
431
+ />
432
+ </div>
433
+ <div className="max-h-64 overflow-y-auto space-y-1">
434
+ {installedIntegrations
435
+ .filter((i) => {
436
+ if (!integrationSearch) return true;
437
+ const q = integrationSearch.toLowerCase();
438
+ return (i.name || i.id).toLowerCase().includes(q) || (i.description || '').toLowerCase().includes(q);
439
+ })
440
+ .map((integration) => {
441
+ const active = selectedIntegrations.includes(integration.id);
442
+ const configured = integration.configured !== false;
443
+ const logoUrl = INTEGRATION_LOGOS[integration.id];
444
+ const roleMatch = selectedRole && Array.isArray(integration.roles) && integration.roles.includes(selectedRole);
445
+ return (
446
+ <button
447
+ key={integration.id}
448
+ onClick={() => {
449
+ if (!configured) return;
450
+ setSelectedIntegrations((prev) =>
451
+ active ? prev.filter((i) => i !== integration.id) : [...prev, integration.id]
452
+ );
453
+ }}
454
+ disabled={!configured}
455
+ className={cn(
456
+ 'w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-left transition-colors',
457
+ configured ? 'cursor-pointer' : 'opacity-40 cursor-not-allowed',
458
+ active
459
+ ? 'bg-accent/10 border border-accent/25'
460
+ : configured
461
+ ? 'hover:bg-surface-3 border border-transparent'
462
+ : 'border border-transparent',
463
+ )}
464
+ >
465
+ {logoUrl ? (
466
+ <img src={logoUrl} alt="" className="w-4 h-4 flex-shrink-0" />
467
+ ) : (
468
+ <Plug size={12} className={active ? 'text-accent' : 'text-text-3'} />
469
+ )}
470
+ <div className="flex-1 min-w-0">
471
+ <div className="flex items-center gap-1.5">
472
+ <span className="text-xs font-semibold text-text-0 font-sans truncate">{integration.name || integration.id}</span>
473
+ {!configured && (
474
+ <span className="text-2xs text-text-4 font-sans">(not configured)</span>
475
+ )}
476
+ {configured && roleMatch && (
477
+ <span className="text-2xs font-mono text-accent/70 bg-accent/8 px-1 py-px rounded">
478
+ rec
479
+ </span>
480
+ )}
481
+ </div>
482
+ {integration.description && (
483
+ <div className="text-2xs text-text-3 font-sans truncate">{integration.description}</div>
484
+ )}
485
+ {!configured && (
486
+ <div className="text-2xs text-text-4 font-sans">Configure in Marketplace</div>
487
+ )}
488
+ </div>
489
+ {active && <CheckMark />}
490
+ </button>
491
+ );
492
+ })}
493
+ {installedIntegrations.length === 0 && (
494
+ <div className="text-center py-6 text-xs text-text-3 font-sans">
495
+ No integrations installed. Visit the Marketplace to install integrations.
496
+ </div>
497
+ )}
498
+ </div>
499
+ </div>
500
+ </DialogContent>
501
+ </Dialog>
502
+
503
+ {/* Approval mode */}
504
+ {selectedIntegrations.length > 0 && (
505
+ <div className="space-y-1.5">
506
+ <label className="text-xs font-medium text-text-2 font-sans">Integration Approvals</label>
507
+ <div className="flex gap-1.5">
508
+ <button
509
+ onClick={() => setIntegrationApproval('manual')}
510
+ className={cn(
511
+ 'flex-1 flex items-center gap-2 px-3 py-2 rounded-md border text-left transition-all cursor-pointer',
512
+ integrationApproval === 'manual'
513
+ ? 'border-accent bg-accent/5'
514
+ : 'border-border-subtle bg-surface-1 hover:border-border',
515
+ )}
516
+ >
517
+ <Shield size={13} className={integrationApproval === 'manual' ? 'text-accent' : 'text-text-3'} />
518
+ <div>
519
+ <div className="text-2xs font-semibold text-text-0 font-sans">Manual</div>
520
+ <div className="text-2xs text-text-3 font-sans">You approve each action</div>
521
+ </div>
522
+ </button>
523
+ <button
524
+ onClick={() => setIntegrationApproval('auto')}
525
+ className={cn(
526
+ 'flex-1 flex items-center gap-2 px-3 py-2 rounded-md border text-left transition-all cursor-pointer',
527
+ integrationApproval === 'auto'
528
+ ? 'border-warning bg-warning/5'
529
+ : 'border-border-subtle bg-surface-1 hover:border-border',
530
+ )}
531
+ >
532
+ <Sparkles size={13} className={integrationApproval === 'auto' ? 'text-warning' : 'text-text-3'} />
533
+ <div>
534
+ <div className="text-2xs font-semibold text-text-0 font-sans">Auto</div>
535
+ <div className="text-2xs text-text-3 font-sans">Agent acts without asking</div>
536
+ </div>
537
+ </button>
538
+ </div>
539
+ </div>
540
+ )}
541
+
331
542
  </div>
332
543
  )}
333
544
  </div>
@@ -5,8 +5,12 @@ import { fmtNum } from '../../lib/format';
5
5
 
6
6
  const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, totalInput = 0, size = 140 }) {
7
7
  const canvasRef = useRef(null);
8
- const total = cacheRead + cacheCreation + totalInput;
9
- const hitRate = total > 0 ? (cacheRead / total) * 100 : 0;
8
+ // Cache hit rate denominator is only cacheable tokens (reads + creation).
9
+ // Fresh inputTokens are conversation turns that were never cache-eligible;
10
+ // including them falsely depresses the rate.
11
+ const cacheable = cacheRead + cacheCreation;
12
+ const total = cacheable; // used only by arc-fill and hit-rate
13
+ const hitRate = cacheable > 0 ? (cacheRead / cacheable) * 100 : 0;
10
14
 
11
15
  useEffect(() => {
12
16
  const canvas = canvasRef.current;