groove-dev 0.27.142 → 0.27.144
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/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
- package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
- package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
- package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
- package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
- package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
- package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
- package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
- package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
- package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
- package/node_modules/@groove-dev/gui/src/app.css +35 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
- package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
- package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
- package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +1086 -6532
- package/packages/daemon/src/gateways/manager.js +35 -1
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/journalist.js +23 -13
- package/packages/daemon/src/mlx-server.js +365 -0
- package/packages/daemon/src/model-lab.js +308 -12
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/local.js +36 -8
- package/packages/daemon/src/registry.js +21 -5
- package/packages/daemon/src/routes/agents.js +889 -0
- package/packages/daemon/src/routes/coordination.js +318 -0
- package/packages/daemon/src/routes/files.js +751 -0
- package/packages/daemon/src/routes/integrations.js +485 -0
- package/packages/daemon/src/routes/network.js +1784 -0
- package/packages/daemon/src/routes/providers.js +755 -0
- package/packages/daemon/src/routes/schedules.js +110 -0
- package/packages/daemon/src/routes/teams.js +650 -0
- package/packages/daemon/src/scheduler.js +456 -24
- package/packages/daemon/src/teams.js +1 -1
- package/packages/daemon/src/validate.js +38 -1
- package/packages/daemon/templates/mlx-setup.json +12 -0
- package/packages/daemon/templates/tgi-setup.json +1 -1
- package/packages/daemon/templates/vllm-setup.json +1 -1
- package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
- package/packages/gui/src/app.css +35 -0
- package/packages/gui/src/components/agents/agent-config.jsx +1 -128
- package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/code-review.jsx +159 -122
- package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/packages/gui/src/components/automations/automation-card.jsx +274 -0
- package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
- package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
- package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/packages/gui/src/components/network/network-health.jsx +2 -2
- package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/packages/gui/src/components/ui/sheet.jsx +5 -2
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +24 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +34 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +452 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +227 -0
- package/packages/gui/src/stores/slices/editor-slice.js +285 -0
- package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/packages/gui/src/stores/slices/network-slice.js +361 -0
- package/packages/gui/src/stores/slices/preview-slice.js +109 -0
- package/packages/gui/src/stores/slices/providers-slice.js +897 -0
- package/packages/gui/src/stores/slices/teams-slice.js +413 -0
- package/packages/gui/src/stores/slices/ui-slice.js +98 -0
- package/packages/gui/src/views/agents.jsx +5 -5
- package/packages/gui/src/views/dashboard.jsx +12 -13
- package/packages/gui/src/views/marketplace.jsx +191 -3
- package/packages/gui/src/views/model-lab.jsx +17 -6
- package/packages/gui/src/views/models.jsx +410 -509
- package/packages/gui/src/views/network.jsx +3 -3
- package/packages/gui/src/views/settings.jsx +81 -94
- package/packages/gui/src/views/teams.jsx +40 -483
- package/SECURITY_SWEEP.md +0 -228
- package/TRAINING_DATA_v4.md +0 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
- package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
import { api } from '../../lib/api';
|
|
4
|
+
|
|
5
|
+
export const createTeamsSlice = (set, get) => ({
|
|
6
|
+
// ── Teams ─────────────────────────────────────────────────
|
|
7
|
+
teams: [],
|
|
8
|
+
archivedTeams: [],
|
|
9
|
+
activeTeamId: localStorage.getItem('groove:activeTeamId') || null,
|
|
10
|
+
|
|
11
|
+
// ── Team Launch Config (set during planner spawn, cascades to team) ──
|
|
12
|
+
teamLaunchConfig: null, // { provider, model, reasoningEffort, temperature, verbosity, mode }
|
|
13
|
+
|
|
14
|
+
// ── Team Builder ──────────────────────────────────────────
|
|
15
|
+
teamBuilderOpen: false,
|
|
16
|
+
teamBuilderRoles: [],
|
|
17
|
+
teamBuilderSettings: { provider: null, model: null, reasoningEffort: 50, temperature: 0.5 },
|
|
18
|
+
teamBuilderTask: '',
|
|
19
|
+
teamTemplates: { builtIn: [], custom: [] },
|
|
20
|
+
|
|
21
|
+
// ── Recommended Team ──────────────────────────────────────
|
|
22
|
+
recommendedTeam: null, // { name, agents: [...] } from planner
|
|
23
|
+
_delegatingTeamIds: new Set(),
|
|
24
|
+
|
|
25
|
+
// ── Teams ─────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
async fetchTeams() {
|
|
28
|
+
try {
|
|
29
|
+
const data = await api.get('/teams');
|
|
30
|
+
let teams = data.teams || [];
|
|
31
|
+
const defaultTeamId = data.defaultTeamId;
|
|
32
|
+
try {
|
|
33
|
+
const saved = JSON.parse(localStorage.getItem('groove:teamOrder') || '[]');
|
|
34
|
+
if (saved.length) {
|
|
35
|
+
const byId = Object.fromEntries(teams.map((t) => [t.id, t]));
|
|
36
|
+
const ordered = saved.filter((id) => byId[id]).map((id) => byId[id]);
|
|
37
|
+
const remaining = teams.filter((t) => !saved.includes(t.id));
|
|
38
|
+
teams = [...ordered, ...remaining];
|
|
39
|
+
}
|
|
40
|
+
} catch {}
|
|
41
|
+
const { activeTeamId } = get();
|
|
42
|
+
const ids = teams.map((t) => t.id);
|
|
43
|
+
const resolved = ids.includes(activeTeamId) ? activeTeamId : defaultTeamId;
|
|
44
|
+
set({ teams, activeTeamId: resolved });
|
|
45
|
+
if (resolved) localStorage.setItem('groove:activeTeamId', resolved);
|
|
46
|
+
} catch { /* ignore */ }
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
switchTeam(id) {
|
|
50
|
+
const { activeTeamId, detailPanel, teamDetailPanels, teamPreviews } = get();
|
|
51
|
+
const updated = { ...teamDetailPanels };
|
|
52
|
+
if (activeTeamId) updated[activeTeamId] = detailPanel;
|
|
53
|
+
const restored = updated[id] || null;
|
|
54
|
+
const tp = teamPreviews[id];
|
|
55
|
+
const previewUpdate = tp
|
|
56
|
+
? { previewState: { url: tp.url, teamId: id, kind: tp.kind, deviceSize: 'desktop', screenshotMode: false } }
|
|
57
|
+
: {};
|
|
58
|
+
set({ activeTeamId: id, detailPanel: restored, teamDetailPanels: updated, ...previewUpdate });
|
|
59
|
+
localStorage.setItem('groove:activeTeamId', id);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async createTeam(name, workingDir, mode) {
|
|
63
|
+
try {
|
|
64
|
+
const body = { name };
|
|
65
|
+
if (workingDir) body.workingDir = workingDir;
|
|
66
|
+
if (mode) body.mode = mode;
|
|
67
|
+
const team = await api.post('/teams', body);
|
|
68
|
+
// Only set activeTeamId — the WS team:created handler adds to the teams array
|
|
69
|
+
set({ activeTeamId: team.id });
|
|
70
|
+
localStorage.setItem('groove:activeTeamId', team.id);
|
|
71
|
+
get().addToast('success', `Team "${name}" created`);
|
|
72
|
+
return team;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
get().addToast('error', 'Failed to create team', err.message);
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async archiveTeam(id) {
|
|
80
|
+
const team = get().teams.find((t) => t.id === id);
|
|
81
|
+
try {
|
|
82
|
+
await api.delete(`/teams/${encodeURIComponent(id)}`);
|
|
83
|
+
const wiped = team?.isDefault ? 'wiped' : 'archived';
|
|
84
|
+
get().addToast('success', `Team "${team?.name}" ${wiped}`, wiped === 'archived' ? 'Files preserved — restore anytime from Archived Teams' : undefined);
|
|
85
|
+
get().fetchArchivedTeams();
|
|
86
|
+
} catch (err) {
|
|
87
|
+
get().addToast('error', 'Failed to archive team', err.message);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async deleteTeamPermanently(id) {
|
|
92
|
+
const team = get().teams.find((t) => t.id === id);
|
|
93
|
+
try {
|
|
94
|
+
await api.delete(`/teams/${encodeURIComponent(id)}?permanent=true`);
|
|
95
|
+
get().addToast('success', `Team "${team?.name}" permanently deleted`);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
get().addToast('error', 'Failed to delete team', err.message);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async deleteTeam(id) {
|
|
102
|
+
return get().archiveTeam(id);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
reorderTeams(fromIndex, toIndex) {
|
|
106
|
+
const teams = [...get().teams];
|
|
107
|
+
const [moved] = teams.splice(fromIndex, 1);
|
|
108
|
+
teams.splice(toIndex, 0, moved);
|
|
109
|
+
set({ teams });
|
|
110
|
+
try { localStorage.setItem('groove:teamOrder', JSON.stringify(teams.map((t) => t.id))); } catch {}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async fetchArchivedTeams() {
|
|
114
|
+
try {
|
|
115
|
+
const data = await api.get('/teams/archived');
|
|
116
|
+
set({ archivedTeams: data.archived || data.teams || [] });
|
|
117
|
+
} catch { /* endpoint may not exist yet */ }
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async restoreTeam(archivedId) {
|
|
121
|
+
try {
|
|
122
|
+
await api.post(`/teams/archived/${encodeURIComponent(archivedId)}/restore`);
|
|
123
|
+
get().addToast('success', 'Team restored');
|
|
124
|
+
get().fetchArchivedTeams();
|
|
125
|
+
} catch (err) {
|
|
126
|
+
get().addToast('error', 'Failed to restore team', err.message);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async purgeTeam(archivedId) {
|
|
131
|
+
try {
|
|
132
|
+
await api.delete(`/teams/archived/${encodeURIComponent(archivedId)}`);
|
|
133
|
+
get().addToast('info', 'Archived team permanently deleted');
|
|
134
|
+
get().fetchArchivedTeams();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
get().addToast('error', 'Failed to purge team', err.message);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
async cloneTeam(id) {
|
|
141
|
+
const team = get().teams.find((t) => t.id === id);
|
|
142
|
+
if (!team) return;
|
|
143
|
+
const sourceAgents = get().agents.filter((a) => a.teamId === id);
|
|
144
|
+
try {
|
|
145
|
+
const newTeam = await api.post('/teams', { name: `${team.name} (copy)` });
|
|
146
|
+
set({ activeTeamId: newTeam.id });
|
|
147
|
+
localStorage.setItem('groove:activeTeamId', newTeam.id);
|
|
148
|
+
for (const agent of sourceAgents) {
|
|
149
|
+
await api.post('/agents', {
|
|
150
|
+
role: agent.role,
|
|
151
|
+
name: agent.name,
|
|
152
|
+
provider: agent.provider,
|
|
153
|
+
model: agent.model,
|
|
154
|
+
scope: agent.scope,
|
|
155
|
+
teamId: newTeam.id,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
get().addToast('success', `Cloned "${team.name}" with ${sourceAgents.length} agent${sourceAgents.length !== 1 ? 's' : ''}`);
|
|
159
|
+
return newTeam;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
get().addToast('error', 'Failed to clone team', err.message);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async renameTeam(id, name) {
|
|
166
|
+
try {
|
|
167
|
+
const team = await api.patch(`/teams/${encodeURIComponent(id)}`, { name });
|
|
168
|
+
set((s) => ({ teams: s.teams.map((t) => (t.id === id ? team : t)) }));
|
|
169
|
+
return team;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
get().addToast('error', 'Failed to rename team', err.message);
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
async promoteTeam(id) {
|
|
177
|
+
try {
|
|
178
|
+
const team = await api.post(`/teams/${encodeURIComponent(id)}/promote`);
|
|
179
|
+
set((s) => ({ teams: s.teams.filter((t) => t.id !== id) }));
|
|
180
|
+
get().addToast('success', 'Team promoted — files moved to project directory');
|
|
181
|
+
return team;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
get().addToast('error', 'Failed to promote team', err.message);
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// ── Recommended Team ──────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
async checkRecommendedTeam() {
|
|
191
|
+
try {
|
|
192
|
+
const data = await api.get('/recommended-team');
|
|
193
|
+
if (!data || !data.agents?.length) {
|
|
194
|
+
set({ recommendedTeam: null });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check if all recommended roles already exist in the planner's team.
|
|
199
|
+
// If so, auto-delegate instead of showing the "Launch Team" modal.
|
|
200
|
+
const teamId = data.teamId || null;
|
|
201
|
+
|
|
202
|
+
if (teamId) {
|
|
203
|
+
const teamAgents = get().agents.filter((a) => a.teamId === teamId && a.role !== 'planner');
|
|
204
|
+
const phase1Roles = data.agents.filter((a) => !a.phase || a.phase === 1).map((a) => a.role);
|
|
205
|
+
const allExist = phase1Roles.every((role) => teamAgents.some((a) => a.role === role));
|
|
206
|
+
|
|
207
|
+
if (allExist && phase1Roles.length > 0) {
|
|
208
|
+
// Guard: skip if already delegating for this team (poll race)
|
|
209
|
+
if (get()._delegatingTeamIds.has(teamId)) return;
|
|
210
|
+
set((s) => ({ recommendedTeam: null, _delegatingTeamIds: new Set([...s._delegatingTeamIds, teamId]) }));
|
|
211
|
+
try {
|
|
212
|
+
const tlc = get().teamLaunchConfig;
|
|
213
|
+
const result = await api.post('/recommended-team/launch', {
|
|
214
|
+
teamId,
|
|
215
|
+
...(tlc?.provider && { teamProvider: tlc.provider }),
|
|
216
|
+
...(tlc?.model && { teamModel: tlc.model }),
|
|
217
|
+
...(tlc?.reasoningEffort != null && { teamReasoningEffort: tlc.reasoningEffort }),
|
|
218
|
+
...(tlc?.temperature != null && { teamTemperature: tlc.temperature }),
|
|
219
|
+
});
|
|
220
|
+
const agents = result.agents || [];
|
|
221
|
+
const failures = result.failed || [];
|
|
222
|
+
const names = agents.map((a) => a.name).join(', ') || '';
|
|
223
|
+
|
|
224
|
+
if (agents.length === 0 && failures.length > 0) {
|
|
225
|
+
get().addToast('error', 'Delegation failed', failures.map(f => f.role + ': ' + f.error).join(', '));
|
|
226
|
+
} else {
|
|
227
|
+
get().addToast('success', 'Planner delegated work', names ? `→ ${names}` : undefined);
|
|
228
|
+
if (failures.length > 0) {
|
|
229
|
+
get().addToast('warning', `${failures.length} agent(s) failed to spawn`, failures.map(f => f.role + ': ' + f.error).join(', '));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (agents.length > 0) {
|
|
233
|
+
set((s) => ({
|
|
234
|
+
thinkingAgents: new Set([...s.thinkingAgents, ...agents.map((a) => a.id)]),
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
} finally {
|
|
238
|
+
set((s) => {
|
|
239
|
+
const next = new Set(s._delegatingTeamIds);
|
|
240
|
+
next.delete(teamId);
|
|
241
|
+
return { _delegatingTeamIds: next };
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// New agents needed — show the modal for approval
|
|
249
|
+
set({ recommendedTeam: { ...data, teamId: data.teamId || null } });
|
|
250
|
+
} catch {
|
|
251
|
+
set({ recommendedTeam: null });
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
async launchRecommendedTeam(modifiedAgents) {
|
|
256
|
+
try {
|
|
257
|
+
const teamId = get().recommendedTeam?.teamId || null;
|
|
258
|
+
const tlc = get().teamLaunchConfig;
|
|
259
|
+
set({ recommendedTeam: null }); // Dismiss modal immediately
|
|
260
|
+
get().addToast('info', 'Launching team...');
|
|
261
|
+
const body = {
|
|
262
|
+
...(modifiedAgents && { agents: modifiedAgents }),
|
|
263
|
+
...(teamId && { teamId }),
|
|
264
|
+
...(tlc?.provider && { teamProvider: tlc.provider }),
|
|
265
|
+
...(tlc?.model && { teamModel: tlc.model }),
|
|
266
|
+
...(tlc?.reasoningEffort != null && { teamReasoningEffort: tlc.reasoningEffort }),
|
|
267
|
+
...(tlc?.temperature != null && { teamTemperature: tlc.temperature }),
|
|
268
|
+
...(tlc?.verbosity != null && { teamVerbosity: tlc.verbosity }),
|
|
269
|
+
};
|
|
270
|
+
const result = await api.post('/recommended-team/launch', body);
|
|
271
|
+
const totalOk = (result.launched || 0) + (result.reused || 0);
|
|
272
|
+
const failures = result.failed || [];
|
|
273
|
+
|
|
274
|
+
if (totalOk === 0 && failures.length > 0) {
|
|
275
|
+
get().addToast('error', 'Team launch failed', failures.map(f => f.role + ': ' + f.error).join(', '));
|
|
276
|
+
} else {
|
|
277
|
+
const sub = [
|
|
278
|
+
result.phase2Pending ? `${result.phase2Pending} QC queued` : '',
|
|
279
|
+
result.projectDir ? `→ ${result.projectDir}/` : '',
|
|
280
|
+
].filter(Boolean).join(' · ');
|
|
281
|
+
get().addToast('success', `Launched ${totalOk} agents`, sub || undefined);
|
|
282
|
+
if (failures.length > 0) {
|
|
283
|
+
get().addToast('warning', `${failures.length} agent(s) failed to spawn`, failures.map(f => f.role + ': ' + f.error).join(', '));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Set thinking indicator for all launched/reused agents
|
|
287
|
+
const launchedAgents = result.agents || [];
|
|
288
|
+
if (launchedAgents.length > 0) {
|
|
289
|
+
set((s) => ({
|
|
290
|
+
thinkingAgents: new Set([...s.thinkingAgents, ...launchedAgents.map((a) => a.id)]),
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
// Clean up stale files — scoped to the launched team so plans in other
|
|
294
|
+
// teams' workspaces survive. The launch endpoint already unlinks the
|
|
295
|
+
// exact plan it read; this is a belt-and-suspenders sweep.
|
|
296
|
+
const launchedTeamId = body?.teamId || result?.teamId || null;
|
|
297
|
+
if (launchedTeamId) {
|
|
298
|
+
api.post('/cleanup', { teamId: launchedTeamId }).catch(() => {});
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
} catch (err) {
|
|
302
|
+
get().addToast('error', 'Launch failed', err.message);
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// ── Team Builder ──────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
openTeamBuilder() { set({ teamBuilderOpen: true }); },
|
|
310
|
+
closeTeamBuilder() {
|
|
311
|
+
set({
|
|
312
|
+
teamBuilderOpen: false,
|
|
313
|
+
teamBuilderRoles: [],
|
|
314
|
+
teamBuilderSettings: { provider: null, model: null, reasoningEffort: 50, temperature: 0.5 },
|
|
315
|
+
teamBuilderTask: '',
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
addTeamBuilderRole(role) {
|
|
319
|
+
set((s) => ({
|
|
320
|
+
teamBuilderRoles: [...s.teamBuilderRoles, {
|
|
321
|
+
role, name: '', provider: null, model: null,
|
|
322
|
+
reasoningEffort: null, temperature: null, prompt: '',
|
|
323
|
+
}],
|
|
324
|
+
}));
|
|
325
|
+
},
|
|
326
|
+
removeTeamBuilderRole(index) {
|
|
327
|
+
set((s) => ({ teamBuilderRoles: s.teamBuilderRoles.filter((_, i) => i !== index) }));
|
|
328
|
+
},
|
|
329
|
+
updateTeamBuilderRole(index, updates) {
|
|
330
|
+
set((s) => ({
|
|
331
|
+
teamBuilderRoles: s.teamBuilderRoles.map((r, i) => i === index ? { ...r, ...updates } : r),
|
|
332
|
+
}));
|
|
333
|
+
},
|
|
334
|
+
applyTemplate(template) {
|
|
335
|
+
set({
|
|
336
|
+
teamBuilderRoles: (template.roles || []).map((r) => ({
|
|
337
|
+
role: typeof r === 'string' ? r : r.role,
|
|
338
|
+
name: '', provider: null, model: null,
|
|
339
|
+
reasoningEffort: null, temperature: null, prompt: '',
|
|
340
|
+
})),
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
setTeamBuilderSettings(settings) {
|
|
344
|
+
set((s) => ({ teamBuilderSettings: { ...s.teamBuilderSettings, ...settings } }));
|
|
345
|
+
},
|
|
346
|
+
setTeamBuilderTask(task) { set({ teamBuilderTask: task }); },
|
|
347
|
+
|
|
348
|
+
async fetchTeamTemplates() {
|
|
349
|
+
try {
|
|
350
|
+
const data = await api.get('/team-templates');
|
|
351
|
+
const builtIn = [];
|
|
352
|
+
const custom = [];
|
|
353
|
+
for (const [key, tmpl] of Object.entries(data || {})) {
|
|
354
|
+
const entry = { ...tmpl, name: key };
|
|
355
|
+
if (tmpl.builtIn) builtIn.push(entry);
|
|
356
|
+
else custom.push(entry);
|
|
357
|
+
}
|
|
358
|
+
set({ teamTemplates: { builtIn, custom } });
|
|
359
|
+
} catch { /* endpoint may not exist yet */ }
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
async saveTeamTemplate(name) {
|
|
363
|
+
try {
|
|
364
|
+
const { teamBuilderRoles, teamBuilderSettings } = get();
|
|
365
|
+
await api.post('/team-templates', {
|
|
366
|
+
name,
|
|
367
|
+
roles: teamBuilderRoles.map((r) => r.role),
|
|
368
|
+
settings: teamBuilderSettings,
|
|
369
|
+
});
|
|
370
|
+
get().addToast('success', `Template "${name}" saved`);
|
|
371
|
+
get().fetchTeamTemplates();
|
|
372
|
+
} catch (err) {
|
|
373
|
+
get().addToast('error', 'Failed to save template', err.message);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
async deleteTeamTemplate(name) {
|
|
378
|
+
try {
|
|
379
|
+
await api.delete(`/team-templates/${encodeURIComponent(name)}`);
|
|
380
|
+
get().addToast('info', `Template "${name}" deleted`);
|
|
381
|
+
get().fetchTeamTemplates();
|
|
382
|
+
} catch (err) {
|
|
383
|
+
get().addToast('error', 'Failed to delete template', err.message);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
async launchTeamBuilder() {
|
|
388
|
+
const { teamBuilderRoles, teamBuilderSettings, teamBuilderTask, activeTeamId } = get();
|
|
389
|
+
if (teamBuilderRoles.length === 0) return;
|
|
390
|
+
set({ teamLaunchConfig: {
|
|
391
|
+
provider: teamBuilderSettings.provider || null,
|
|
392
|
+
model: teamBuilderSettings.model || null,
|
|
393
|
+
reasoningEffort: teamBuilderSettings.reasoningEffort,
|
|
394
|
+
temperature: teamBuilderSettings.temperature,
|
|
395
|
+
}});
|
|
396
|
+
get().closeTeamBuilder();
|
|
397
|
+
try {
|
|
398
|
+
const body = {
|
|
399
|
+
task: teamBuilderTask,
|
|
400
|
+
roles: teamBuilderRoles,
|
|
401
|
+
settings: teamBuilderSettings,
|
|
402
|
+
launchMode: 'plan-first',
|
|
403
|
+
teamId: activeTeamId,
|
|
404
|
+
};
|
|
405
|
+
const result = await api.post('/team-builder/launch', body);
|
|
406
|
+
get().addToast('success', 'Planner spawned — team will build automatically');
|
|
407
|
+
return result;
|
|
408
|
+
} catch (err) {
|
|
409
|
+
get().addToast('error', 'Team launch failed', err.message);
|
|
410
|
+
throw err;
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
import { loadJSON, persistJSON } from '../helpers.js';
|
|
4
|
+
|
|
5
|
+
let toastCounter = 0;
|
|
6
|
+
|
|
7
|
+
export const createUiSlice = (set, get) => ({
|
|
8
|
+
// ── Navigation ────────────────────────────────────────────
|
|
9
|
+
activeView: 'agents', // 'agents' | 'editor' | 'dashboard' | 'marketplace' | 'teams' | 'settings'
|
|
10
|
+
detailPanel: null, // null | { type: 'agent', agentId } | { type: 'spawn' } | { type: 'journalist' }
|
|
11
|
+
teamDetailPanels: {}, // { [teamId]: detailPanel } — persists panel state per team
|
|
12
|
+
commandPaletteOpen: false,
|
|
13
|
+
quickConnectOpen: false,
|
|
14
|
+
upgradeModalOpen: false,
|
|
15
|
+
|
|
16
|
+
// ── Node expansion (click-to-open persistent panels) ───────
|
|
17
|
+
expandedNodes: loadJSON('groove:expandedNodes'),
|
|
18
|
+
|
|
19
|
+
// ── Layout persistence ────────────────────────────────────
|
|
20
|
+
detailPanelWidth: Number(localStorage.getItem('groove:detailWidth')) || 480,
|
|
21
|
+
terminalVisible: localStorage.getItem('groove:terminalVisible') === 'true',
|
|
22
|
+
terminalHeight: Number(localStorage.getItem('groove:terminalHeight')) || 260,
|
|
23
|
+
terminalFullHeight: false,
|
|
24
|
+
|
|
25
|
+
// ── Toasts ────────────────────────────────────────────────
|
|
26
|
+
toasts: [],
|
|
27
|
+
|
|
28
|
+
// ── Version / Auto-Update ──────────────────────────────────
|
|
29
|
+
version: null,
|
|
30
|
+
updateReady: null,
|
|
31
|
+
updateProgress: null,
|
|
32
|
+
updateModalOpen: false,
|
|
33
|
+
|
|
34
|
+
// ── Navigation ────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
setActiveView(view) { set({ activeView: view }); },
|
|
37
|
+
|
|
38
|
+
openDetail(descriptor) {
|
|
39
|
+
const tid = get().activeTeamId;
|
|
40
|
+
set((s) => ({ detailPanel: descriptor, teamDetailPanels: { ...s.teamDetailPanels, [tid]: descriptor } }));
|
|
41
|
+
},
|
|
42
|
+
closeDetail() {
|
|
43
|
+
const tid = get().activeTeamId;
|
|
44
|
+
set((s) => ({ detailPanel: null, teamDetailPanels: { ...s.teamDetailPanels, [tid]: null } }));
|
|
45
|
+
},
|
|
46
|
+
clearSelection() {
|
|
47
|
+
const tid = get().activeTeamId;
|
|
48
|
+
set((s) => ({ detailPanel: null, teamDetailPanels: { ...s.teamDetailPanels, [tid]: null } }));
|
|
49
|
+
},
|
|
50
|
+
toggleCommandPalette() { set((s) => ({ commandPaletteOpen: !s.commandPaletteOpen })); },
|
|
51
|
+
toggleQuickConnect() { set((s) => ({ quickConnectOpen: !s.quickConnectOpen })); },
|
|
52
|
+
setUpgradeModalOpen: (open) => set({ upgradeModalOpen: open }),
|
|
53
|
+
|
|
54
|
+
setDetailPanelWidth(w) {
|
|
55
|
+
set({ detailPanelWidth: w });
|
|
56
|
+
localStorage.setItem('groove:detailWidth', String(w));
|
|
57
|
+
},
|
|
58
|
+
setTerminalVisible(v) {
|
|
59
|
+
set({ terminalVisible: v });
|
|
60
|
+
localStorage.setItem('groove:terminalVisible', String(v));
|
|
61
|
+
},
|
|
62
|
+
setTerminalHeight(h) {
|
|
63
|
+
set({ terminalHeight: h });
|
|
64
|
+
localStorage.setItem('groove:terminalHeight', String(h));
|
|
65
|
+
},
|
|
66
|
+
setTerminalFullHeight(v) { set({ terminalFullHeight: v }); },
|
|
67
|
+
|
|
68
|
+
toggleNodeExpanded(id) {
|
|
69
|
+
const expanded = { ...get().expandedNodes };
|
|
70
|
+
expanded[id] = !expanded[id];
|
|
71
|
+
if (!expanded[id]) delete expanded[id];
|
|
72
|
+
set({ expandedNodes: expanded });
|
|
73
|
+
persistJSON('groove:expandedNodes', expanded);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// ── Toasts ────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
addToast(type, message, detail, action, options = {}) {
|
|
79
|
+
const id = ++toastCounter;
|
|
80
|
+
const persistent = !!options.persistent;
|
|
81
|
+
const duration = options.duration;
|
|
82
|
+
const actions = options.actions;
|
|
83
|
+
set((s) => ({ toasts: [...s.toasts, { id, type, message, detail, action, actions, persistent, duration }] }));
|
|
84
|
+
},
|
|
85
|
+
removeToast(id) {
|
|
86
|
+
set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) }));
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
installUpdate() {
|
|
90
|
+
window.groove?.update?.installUpdate();
|
|
91
|
+
},
|
|
92
|
+
setUpdateModalOpen(open) {
|
|
93
|
+
set({ updateModalOpen: open });
|
|
94
|
+
},
|
|
95
|
+
checkForUpdate() {
|
|
96
|
+
window.groove?.update?.checkForUpdate();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -79,7 +79,7 @@ function saveTeamViewport(teamId, viewport) {
|
|
|
79
79
|
/* ── Team Tab Bar (IDE-style) ──────────────────────────────── */
|
|
80
80
|
|
|
81
81
|
function teamStatus(agents, teamId) {
|
|
82
|
-
const ta = agents.filter((a) => a.teamId === teamId);
|
|
82
|
+
const ta = agents.filter((a) => a.teamId === teamId && !a.metadata?.scheduled);
|
|
83
83
|
if (ta.length === 0) return 'idle';
|
|
84
84
|
const running = ta.some((a) => a.status === 'running' || a.status === 'starting');
|
|
85
85
|
if (running) return 'working';
|
|
@@ -173,10 +173,10 @@ export function TeamTabBar() {
|
|
|
173
173
|
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none', WebkitOverflowScrolling: 'touch' }}
|
|
174
174
|
>
|
|
175
175
|
{teams.map((team) => {
|
|
176
|
-
const count = agents.filter((a) => a.teamId === team.id).length;
|
|
176
|
+
const count = agents.filter((a) => a.teamId === team.id && !a.metadata?.scheduled).length;
|
|
177
177
|
const isActive = team.id === activeTeamId;
|
|
178
178
|
const isRenaming = renamingId === team.id;
|
|
179
|
-
const running = agents.filter((a) => a.teamId === team.id && (a.status === 'running' || a.status === 'starting')).length;
|
|
179
|
+
const running = agents.filter((a) => a.teamId === team.id && !a.metadata?.scheduled && (a.status === 'running' || a.status === 'starting')).length;
|
|
180
180
|
|
|
181
181
|
return (
|
|
182
182
|
<ContextMenu key={team.id}>
|
|
@@ -332,7 +332,7 @@ function AgentTreeInner() {
|
|
|
332
332
|
const prevAgentsRef = useRef([]);
|
|
333
333
|
const agents = useMemo(() => {
|
|
334
334
|
const next = allAgents
|
|
335
|
-
.filter((a) => a.teamId === activeTeamId)
|
|
335
|
+
.filter((a) => a.teamId === activeTeamId && !a.metadata?.scheduled)
|
|
336
336
|
.sort((a, b) => (a.name || a.id).localeCompare(b.name || b.id));
|
|
337
337
|
const prev = prevAgentsRef.current;
|
|
338
338
|
if (prev.length === next.length && prev.every((p, i) => p.id === next[i].id && p.status === next[i].status && p.name === next[i].name && p.model === next[i].model && p.tokensUsed === next[i].tokensUsed && p.contextUsage === next[i].contextUsage)) return prev;
|
|
@@ -1579,7 +1579,7 @@ export default function AgentsView() {
|
|
|
1579
1579
|
} catch { /* toast handles */ }
|
|
1580
1580
|
}
|
|
1581
1581
|
|
|
1582
|
-
const teamAgents = allAgents.filter((a) => a.teamId === activeTeamId);
|
|
1582
|
+
const teamAgents = allAgents.filter((a) => a.teamId === activeTeamId && !a.metadata?.scheduled);
|
|
1583
1583
|
const hydrated = useGrooveStore((s) => s.hydrated);
|
|
1584
1584
|
const [showLoader, setShowLoader] = useState(true);
|
|
1585
1585
|
|
|
@@ -18,10 +18,9 @@ import { BarChart3 } from 'lucide-react';
|
|
|
18
18
|
|
|
19
19
|
function DashboardSkeleton() {
|
|
20
20
|
return (
|
|
21
|
-
<div className="flex-1 grid gap-px p-0" style={{
|
|
21
|
+
<div className="flex-1 grid gap-px p-0 bg-surface-3" style={{
|
|
22
22
|
gridTemplateRows: 'auto minmax(0, 1fr) minmax(0, 1fr)',
|
|
23
23
|
gridTemplateColumns: '2fr 2.5fr 1.5fr',
|
|
24
|
-
background: '#282c34',
|
|
25
24
|
}}>
|
|
26
25
|
<div className="col-span-3"><Skeleton className="h-[72px] rounded-none" /></div>
|
|
27
26
|
<Skeleton className="rounded-none" />
|
|
@@ -90,14 +89,14 @@ export default function DashboardView() {
|
|
|
90
89
|
const events = timeline.events || data.events || [];
|
|
91
90
|
|
|
92
91
|
const kpis = [
|
|
93
|
-
{ label: 'Tokens Used', value: fmtNum(tokens.totalTokens), sparkData: kpiHistory.tokens, color: HEX.
|
|
94
|
-
{ label: 'Total Cost', value: fmtDollar(tokens.totalCostUsd), sparkData: kpiHistory.cost, color: HEX.
|
|
95
|
-
{ label: 'Quality', value: avgQuality != null ? `${avgQuality}` : '—', sparkData: kpiHistory.saved, color:
|
|
96
|
-
{ label: 'Cache Rate', value: fmtPct(tokens.cacheHitRate * 100), sparkData: kpiHistory.cache, color: HEX.
|
|
97
|
-
{ label: 'Rotations', value: `${totalRotations}`, sparkData: kpiHistory.efficiency, color: HEX.
|
|
98
|
-
{ label: 'I/O Ratio', value: `${ioRatio}:1`, sparkData: kpiHistory.inputOutput, color: HEX.
|
|
99
|
-
{ label: 'Agents', value: `${runningCount}/${agents.length}`, sparkData: kpiHistory.agents, color: HEX.
|
|
100
|
-
{ label: 'Turns', value: fmtNum(tokens.totalTurns), sparkData: kpiHistory.turns, color: HEX.
|
|
92
|
+
{ label: 'Tokens Used', value: fmtNum(tokens.totalTokens), sparkData: kpiHistory.tokens, color: HEX.text3, hint: 'Total tokens consumed across all agents — input, output, and cache tokens combined.' },
|
|
93
|
+
{ label: 'Total Cost', value: fmtDollar(tokens.totalCostUsd), sparkData: kpiHistory.cost, color: HEX.text3, hint: 'Actual cost reported by providers. Claude Code reports real billing; other providers use estimated rates.' },
|
|
94
|
+
{ label: 'Quality', value: avgQuality != null ? `${avgQuality}` : '—', sparkData: kpiHistory.saved, color: HEX.text3, hint: 'Average session quality score (0-100) across running agents. Based on error rate, repetitions, file churn, and tool success. Below 40 triggers auto-rotation.' },
|
|
95
|
+
{ label: 'Cache Rate', value: fmtPct(tokens.cacheHitRate * 100), sparkData: kpiHistory.cache, color: HEX.text3, hint: 'Percentage of input tokens served from prompt cache. Higher = faster responses and lower cost. Managed by your AI provider.' },
|
|
96
|
+
{ label: 'Rotations', value: `${totalRotations}`, sparkData: kpiHistory.efficiency, color: HEX.text3, hint: 'Total context rotations — includes quality-based (auto), context threshold, natural compaction (provider-managed), and manual rotations.' },
|
|
97
|
+
{ label: 'I/O Ratio', value: `${ioRatio}:1`, sparkData: kpiHistory.inputOutput, color: HEX.text3, hint: 'Ratio of input to output tokens. High ratios mean agents are reading more than writing — common for analysis tasks.' },
|
|
98
|
+
{ label: 'Agents', value: `${runningCount}/${agents.length}`, sparkData: kpiHistory.agents, color: HEX.text3, hint: 'Running agents out of total spawned this session (including completed and crashed).' },
|
|
99
|
+
{ label: 'Turns', value: fmtNum(tokens.totalTurns), sparkData: kpiHistory.turns, color: HEX.text3, hint: 'Total conversation turns across all agents. Each turn is one request-response cycle with the AI provider.' },
|
|
101
100
|
];
|
|
102
101
|
|
|
103
102
|
return (
|
|
@@ -113,8 +112,8 @@ export default function DashboardView() {
|
|
|
113
112
|
|
|
114
113
|
<KpiStrip kpis={kpis} />
|
|
115
114
|
|
|
116
|
-
<div className="flex-1 min-h-0 flex flex-col
|
|
117
|
-
<div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '3fr 1.5fr 1.5fr'
|
|
115
|
+
<div className="flex-1 min-h-0 flex flex-col bg-surface-3 gap-px">
|
|
116
|
+
<div className="min-h-0 flex-1 grid gap-x-px" style={{ gridTemplateColumns: '3fr 1.5fr 1.5fr' }}>
|
|
118
117
|
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 relative">
|
|
119
118
|
<TokenChart data={snapshots} />
|
|
120
119
|
</div>
|
|
@@ -138,7 +137,7 @@ export default function DashboardView() {
|
|
|
138
137
|
</div>
|
|
139
138
|
</div>
|
|
140
139
|
|
|
141
|
-
<div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '2fr 2.5fr 1.5fr'
|
|
140
|
+
<div className="min-h-0 flex-1 grid gap-x-px" style={{ gridTemplateColumns: '2fr 2.5fr 1.5fr' }}>
|
|
142
141
|
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-border">
|
|
143
142
|
<div className="px-3 pt-2.5 pb-1 flex-shrink-0">
|
|
144
143
|
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Agent Fleet</span>
|