groove-dev 0.27.14 → 0.27.17
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/README.md +37 -1
- package/developerID_application.cer +0 -0
- package/node_modules/@groove-dev/daemon/src/api.js +587 -68
- package/node_modules/@groove-dev/daemon/src/classifier.js +24 -0
- package/node_modules/@groove-dev/daemon/src/credentials.js +12 -2
- package/node_modules/@groove-dev/daemon/src/federation/ambassador.js +204 -0
- package/node_modules/@groove-dev/daemon/src/federation/connection.js +359 -0
- package/node_modules/@groove-dev/daemon/src/federation/contracts.js +112 -0
- package/node_modules/@groove-dev/daemon/src/federation/whitelist.js +190 -0
- package/node_modules/@groove-dev/daemon/src/federation.js +166 -7
- package/node_modules/@groove-dev/daemon/src/index.js +172 -19
- package/node_modules/@groove-dev/daemon/src/introducer.js +52 -7
- package/node_modules/@groove-dev/daemon/src/journalist.js +46 -1
- package/node_modules/@groove-dev/daemon/src/memory.js +36 -16
- package/node_modules/@groove-dev/daemon/src/process.js +140 -23
- package/node_modules/@groove-dev/daemon/src/providers/base.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +124 -28
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +104 -17
- package/node_modules/@groove-dev/daemon/src/providers/index.js +17 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +10 -1
- package/node_modules/@groove-dev/daemon/src/rotator.js +93 -30
- package/node_modules/@groove-dev/daemon/src/skills.js +33 -3
- package/node_modules/@groove-dev/daemon/src/terminal-pty.js +9 -1
- package/node_modules/@groove-dev/daemon/src/tool-executor.js +11 -5
- package/node_modules/@groove-dev/daemon/src/toys.js +69 -0
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +24 -5
- package/node_modules/@groove-dev/daemon/templates/toys-catalog.json +242 -0
- package/node_modules/@groove-dev/daemon/test/classifier.test.js +98 -0
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +72 -1
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +117 -0
- package/node_modules/@groove-dev/daemon/test/memory.test.js +37 -1
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +183 -4
- package/node_modules/@groove-dev/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -2
- package/node_modules/@groove-dev/gui/index.html +1 -0
- package/node_modules/@groove-dev/gui/src/app.css +7 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +37 -10
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +21 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +11 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/node_modules/@groove-dev/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +33 -2
- package/node_modules/@groove-dev/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/node_modules/@groove-dev/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/node_modules/@groove-dev/gui/src/components/editor/goto-line.jsx +35 -0
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +12 -6
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +13 -3
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +6 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/node_modules/@groove-dev/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +12 -5
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-activity.jsx +98 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-panel.jsx +290 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-peers.jsx +126 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +110 -67
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/settings/server-detail.jsx +310 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +4 -1
- package/node_modules/@groove-dev/gui/src/components/settings/server-list.jsx +59 -0
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +78 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +144 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/lib/electron.js +15 -0
- package/node_modules/@groove-dev/gui/src/lib/format.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +373 -58
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +148 -42
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +92 -2
- package/node_modules/@groove-dev/gui/src/views/federation.jsx +37 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +2 -42
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +32 -132
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +327 -0
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +162 -0
- package/package.json +1 -1
- package/packages/daemon/src/api.js +587 -68
- package/packages/daemon/src/classifier.js +24 -0
- package/packages/daemon/src/credentials.js +12 -2
- package/packages/daemon/src/federation/ambassador.js +204 -0
- package/packages/daemon/src/federation/connection.js +359 -0
- package/packages/daemon/src/federation/contracts.js +112 -0
- package/packages/daemon/src/federation/whitelist.js +190 -0
- package/packages/daemon/src/federation.js +166 -7
- package/packages/daemon/src/index.js +172 -19
- package/packages/daemon/src/introducer.js +52 -7
- package/packages/daemon/src/journalist.js +46 -1
- package/packages/daemon/src/memory.js +36 -16
- package/packages/daemon/src/process.js +140 -23
- package/packages/daemon/src/providers/base.js +1 -0
- package/packages/daemon/src/providers/claude-code.js +1 -0
- package/packages/daemon/src/providers/codex.js +124 -28
- package/packages/daemon/src/providers/gemini.js +104 -17
- package/packages/daemon/src/providers/index.js +17 -0
- package/packages/daemon/src/registry.js +10 -1
- package/packages/daemon/src/rotator.js +93 -30
- package/packages/daemon/src/skills.js +33 -3
- package/packages/daemon/src/terminal-pty.js +9 -1
- package/packages/daemon/src/tool-executor.js +11 -5
- package/packages/daemon/src/toys.js +69 -0
- package/packages/daemon/src/tunnel-manager.js +24 -5
- package/packages/daemon/templates/toys-catalog.json +242 -0
- package/packages/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/packages/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/packages/gui/dist/index.html +3 -2
- package/packages/gui/index.html +1 -0
- package/packages/gui/src/app.css +7 -0
- package/packages/gui/src/app.jsx +37 -10
- package/packages/gui/src/components/agents/agent-chat.jsx +21 -31
- package/packages/gui/src/components/agents/agent-config.jsx +11 -6
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/packages/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/packages/gui/src/components/editor/code-editor.jsx +33 -2
- package/packages/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/packages/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/packages/gui/src/components/editor/goto-line.jsx +35 -0
- package/packages/gui/src/components/editor/terminal.jsx +12 -6
- package/packages/gui/src/components/layout/activity-bar.jsx +13 -3
- package/packages/gui/src/components/layout/app-shell.jsx +0 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/packages/gui/src/components/layout/command-palette.jsx +6 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/packages/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/packages/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/packages/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +12 -5
- package/packages/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/packages/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/packages/gui/src/components/settings/federation-activity.jsx +98 -0
- package/packages/gui/src/components/settings/federation-panel.jsx +290 -0
- package/packages/gui/src/components/settings/federation-peers.jsx +126 -0
- package/packages/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +110 -67
- package/packages/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/packages/gui/src/components/settings/server-detail.jsx +310 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +4 -1
- package/packages/gui/src/components/settings/server-list.jsx +59 -0
- package/packages/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/packages/gui/src/components/toys/toy-card.jsx +78 -0
- package/packages/gui/src/components/toys/toy-creator.jsx +144 -0
- package/packages/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/packages/gui/src/components/ui/toast.jsx +2 -2
- package/packages/gui/src/lib/electron.js +15 -0
- package/packages/gui/src/lib/format.js +1 -0
- package/packages/gui/src/stores/groove.js +373 -58
- package/packages/gui/src/views/agents.jsx +148 -42
- package/packages/gui/src/views/editor.jsx +92 -2
- package/packages/gui/src/views/federation.jsx +37 -0
- package/packages/gui/src/views/marketplace.jsx +2 -42
- package/packages/gui/src/views/settings.jsx +32 -132
- package/packages/gui/src/views/subscription-panel.jsx +327 -0
- package/packages/gui/src/views/teams.jsx +3 -3
- package/packages/gui/src/views/toys.jsx +162 -0
- package/plans/chat-persistence-refactor.md +154 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +0 -677
- package/packages/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/packages/gui/dist/assets/index-zdzOLAZM.js +0 -677
|
@@ -19,7 +19,7 @@ function persistJSON(key, value) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// Clear stale persisted data on version change
|
|
22
|
-
const STORE_VERSION = '0.22.
|
|
22
|
+
const STORE_VERSION = '0.22.28';
|
|
23
23
|
if (loadJSON('groove:storeVersion') !== STORE_VERSION) {
|
|
24
24
|
localStorage.removeItem('groove:chatHistory');
|
|
25
25
|
localStorage.removeItem('groove:activityLog');
|
|
@@ -42,12 +42,23 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
42
42
|
// ── Gateways ──────────────────────────────────────────────
|
|
43
43
|
gateways: [],
|
|
44
44
|
|
|
45
|
+
// ── Federation ────────────────────────────────────────────
|
|
46
|
+
federation: {
|
|
47
|
+
peers: [],
|
|
48
|
+
whitelist: [],
|
|
49
|
+
connections: [],
|
|
50
|
+
pouchLog: [],
|
|
51
|
+
ambassadors: [],
|
|
52
|
+
selectedPeerId: null,
|
|
53
|
+
},
|
|
54
|
+
|
|
45
55
|
// ── Navigation ────────────────────────────────────────────
|
|
46
56
|
activeView: 'agents', // 'agents' | 'editor' | 'dashboard' | 'marketplace' | 'teams' | 'settings'
|
|
47
57
|
detailPanel: null, // null | { type: 'agent', agentId } | { type: 'spawn' } | { type: 'journalist' }
|
|
48
58
|
teamDetailPanels: {}, // { [teamId]: detailPanel } — persists panel state per team
|
|
49
59
|
commandPaletteOpen: false,
|
|
50
60
|
quickConnectOpen: false,
|
|
61
|
+
upgradeModalOpen: false,
|
|
51
62
|
|
|
52
63
|
// ── Node expansion (click-to-open persistent panels) ───────
|
|
53
64
|
expandedNodes: loadJSON('groove:expandedNodes'),
|
|
@@ -70,6 +81,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
70
81
|
|
|
71
82
|
// ── Recommended Team ──────────────────────────────────────
|
|
72
83
|
recommendedTeam: null, // { name, agents: [...] } from planner
|
|
84
|
+
_delegatingTeamIds: new Set(),
|
|
73
85
|
|
|
74
86
|
// ── Journalist ────────────────────────────────────────────
|
|
75
87
|
journalistStatus: null, // { cycleCount, lastCycleTime, history, lastSynthesis }
|
|
@@ -77,6 +89,16 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
77
89
|
// ── Marketplace Auth ───────────────────────────────────────
|
|
78
90
|
marketplaceUser: null, // { id, displayName, avatar, ... } or null
|
|
79
91
|
marketplaceAuthenticated: false,
|
|
92
|
+
edition: 'community', // 'community' | 'pro' — runtime edition from /edition
|
|
93
|
+
subscription: {
|
|
94
|
+
plan: 'community',
|
|
95
|
+
status: 'none',
|
|
96
|
+
active: false,
|
|
97
|
+
features: [],
|
|
98
|
+
seats: 1,
|
|
99
|
+
periodEnd: null,
|
|
100
|
+
cancelAtPeriodEnd: false,
|
|
101
|
+
},
|
|
80
102
|
|
|
81
103
|
// ── Toasts ────────────────────────────────────────────────
|
|
82
104
|
toasts: [],
|
|
@@ -96,6 +118,10 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
96
118
|
editorTreeCache: {},
|
|
97
119
|
editorChangedFiles: {},
|
|
98
120
|
editorRecentSaves: {},
|
|
121
|
+
editorSidebarWidth: Number(localStorage.getItem('groove:editorSidebarWidth')) || 240,
|
|
122
|
+
|
|
123
|
+
// ── Onboarding ────────────────────────────────────────────
|
|
124
|
+
onboardingComplete: localStorage.getItem('groove:onboardingComplete') === 'true',
|
|
99
125
|
|
|
100
126
|
// ── Connection ────────────────────────────────────────────
|
|
101
127
|
|
|
@@ -116,14 +142,23 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
116
142
|
get().fetchTeams();
|
|
117
143
|
get().fetchApprovals();
|
|
118
144
|
get().checkMarketplaceAuth();
|
|
145
|
+
get().fetchTunnels();
|
|
146
|
+
if (!get().onboardingComplete) get().fetchOnboardingStatus();
|
|
147
|
+
if (window.groove?.auth?.onSubscriptionStatus) {
|
|
148
|
+
window.groove.auth.onSubscriptionStatus((data) => {
|
|
149
|
+
if (data) set({ subscription: { ...get().subscription, ...data } });
|
|
150
|
+
});
|
|
151
|
+
}
|
|
119
152
|
};
|
|
120
153
|
|
|
121
154
|
ws.onmessage = (event) => {
|
|
122
155
|
const msg = JSON.parse(event.data);
|
|
156
|
+
if (!msg || typeof msg !== 'object' || Object.hasOwn(msg, '__proto__') || Object.hasOwn(msg, 'constructor')) return;
|
|
123
157
|
switch (msg.type) {
|
|
124
158
|
case 'state': {
|
|
125
159
|
const timeline = { ...get().tokenTimeline };
|
|
126
160
|
const now = Date.now();
|
|
161
|
+
const liveIds = new Set(msg.data.map((a) => a.id));
|
|
127
162
|
for (const agent of msg.data) {
|
|
128
163
|
if (!timeline[agent.id]) timeline[agent.id] = [];
|
|
129
164
|
const arr = timeline[agent.id];
|
|
@@ -133,15 +168,30 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
133
168
|
if (arr.length > 200) timeline[agent.id] = arr.slice(-200);
|
|
134
169
|
}
|
|
135
170
|
}
|
|
171
|
+
// Prune stale agent data from timeline, chatHistory, activityLog.
|
|
172
|
+
// Without this, localStorage fills with dead agents' data until quota is
|
|
173
|
+
// exceeded and nothing else (e.g. node positions) can save.
|
|
174
|
+
let prunedChat = null, prunedLog = null;
|
|
175
|
+
const st = get();
|
|
176
|
+
for (const id of Object.keys(timeline)) if (!liveIds.has(id)) delete timeline[id];
|
|
177
|
+
for (const id of Object.keys(st.chatHistory)) {
|
|
178
|
+
if (!liveIds.has(id)) { if (!prunedChat) prunedChat = { ...st.chatHistory }; delete prunedChat[id]; }
|
|
179
|
+
}
|
|
180
|
+
for (const id of Object.keys(st.activityLog)) {
|
|
181
|
+
if (!liveIds.has(id)) { if (!prunedLog) prunedLog = { ...st.activityLog }; delete prunedLog[id]; }
|
|
182
|
+
}
|
|
136
183
|
// Only replace agents array if something meaningful changed
|
|
137
184
|
// (prevents React Flow tree flicker on every lastActivity update)
|
|
138
|
-
const prev =
|
|
185
|
+
const prev = st.agents;
|
|
139
186
|
const changed = msg.data.length !== prev.length || msg.data.some((a, i) => {
|
|
140
187
|
const p = prev[i];
|
|
141
188
|
return !p || p.id !== a.id || p.status !== a.status || p.tokensUsed !== a.tokensUsed
|
|
142
189
|
|| p.contextUsage !== a.contextUsage || p.name !== a.name || p.model !== a.model;
|
|
143
190
|
});
|
|
144
|
-
|
|
191
|
+
const nextState = { agents: changed ? msg.data : prev, tokenTimeline: timeline, hydrated: true };
|
|
192
|
+
if (prunedChat) { nextState.chatHistory = prunedChat; persistJSON('groove:chatHistory', prunedChat); }
|
|
193
|
+
if (prunedLog) { nextState.activityLog = prunedLog; persistJSON('groove:activityLog', prunedLog); }
|
|
194
|
+
set(nextState);
|
|
145
195
|
|
|
146
196
|
// Poll for recommended-team.json while a planner is running
|
|
147
197
|
const hasRunningPlanner = msg.data.some((a) => a.role === 'planner' && a.status === 'running');
|
|
@@ -258,6 +308,15 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
258
308
|
const type = msg.status === 'completed' ? 'success' : isKill ? 'info' : 'warning';
|
|
259
309
|
get().addToast(type, text, msg.error ? msg.error.slice(0, 200) : undefined);
|
|
260
310
|
|
|
311
|
+
// Clear thinking indicator — agent is no longer active
|
|
312
|
+
if (get().thinkingAgents.has(msg.agentId)) {
|
|
313
|
+
set((s) => {
|
|
314
|
+
const next = new Set(s.thinkingAgents);
|
|
315
|
+
next.delete(msg.agentId);
|
|
316
|
+
return { thinkingAgents: next };
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
261
320
|
// Log crash error to agent chat so user can see what happened
|
|
262
321
|
if (msg.error && msg.agentId) {
|
|
263
322
|
get().addChatMessage(msg.agentId, 'system', `Crashed: ${msg.error}`);
|
|
@@ -377,6 +436,30 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
377
436
|
set({ gateways: msg.data || [] });
|
|
378
437
|
break;
|
|
379
438
|
|
|
439
|
+
case 'federation:whitelist':
|
|
440
|
+
set((s) => ({ federation: { ...s.federation, whitelist: msg.data || [] } }));
|
|
441
|
+
break;
|
|
442
|
+
|
|
443
|
+
case 'federation:connection':
|
|
444
|
+
set((s) => {
|
|
445
|
+
const conns = [...s.federation.connections];
|
|
446
|
+
const idx = conns.findIndex((c) => c.ip === msg.data?.ip);
|
|
447
|
+
if (idx >= 0) conns[idx] = { ...conns[idx], ...msg.data };
|
|
448
|
+
else conns.push(msg.data);
|
|
449
|
+
return { federation: { ...s.federation, connections: conns } };
|
|
450
|
+
});
|
|
451
|
+
break;
|
|
452
|
+
|
|
453
|
+
case 'federation:pouch':
|
|
454
|
+
case 'federation:pouch-log':
|
|
455
|
+
set((s) => ({
|
|
456
|
+
federation: {
|
|
457
|
+
...s.federation,
|
|
458
|
+
pouchLog: [...s.federation.pouchLog, msg.data].slice(-200),
|
|
459
|
+
},
|
|
460
|
+
}));
|
|
461
|
+
break;
|
|
462
|
+
|
|
380
463
|
case 'tunnel.connected':
|
|
381
464
|
set({ activeTunnelId: msg.data?.id || null });
|
|
382
465
|
get().fetchTunnels();
|
|
@@ -394,6 +477,34 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
394
477
|
set({ savedTunnels: tunnels });
|
|
395
478
|
break;
|
|
396
479
|
}
|
|
480
|
+
|
|
481
|
+
case 'subscription:updated': {
|
|
482
|
+
const subUpdate = { subscription: msg.data };
|
|
483
|
+
if (msg.data?.active === true && !get().marketplaceAuthenticated) {
|
|
484
|
+
subUpdate.marketplaceAuthenticated = true;
|
|
485
|
+
}
|
|
486
|
+
set(subUpdate);
|
|
487
|
+
api.get('/edition').then((ed) => {
|
|
488
|
+
set({
|
|
489
|
+
edition: ed.edition || 'community',
|
|
490
|
+
subscription: {
|
|
491
|
+
plan: ed.plan || 'community',
|
|
492
|
+
status: ed.status || (ed.subscriptionActive ? 'active' : 'none'),
|
|
493
|
+
active: ed.subscriptionActive === true,
|
|
494
|
+
features: ed.features || [],
|
|
495
|
+
seats: ed.seats || 1,
|
|
496
|
+
periodEnd: ed.periodEnd || null,
|
|
497
|
+
cancelAtPeriodEnd: ed.cancelAtPeriodEnd || false,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
}).catch(() => {});
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
case 'auth:expired':
|
|
505
|
+
set({ marketplaceAuthenticated: false, marketplaceUser: null });
|
|
506
|
+
get().addToast('warning', 'Session expired', 'Please sign in again');
|
|
507
|
+
break;
|
|
397
508
|
}
|
|
398
509
|
};
|
|
399
510
|
|
|
@@ -465,7 +576,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
465
576
|
const team = get().teams.find((t) => t.id === id);
|
|
466
577
|
if (team?.isDefault) { get().addToast('warning', 'Cannot delete the default team'); return; }
|
|
467
578
|
try {
|
|
468
|
-
await api.delete(`/teams/${id}`);
|
|
579
|
+
await api.delete(`/teams/${encodeURIComponent(id)}`);
|
|
469
580
|
// WS team:deleted handler removes from array and switches activeTeamId
|
|
470
581
|
get().addToast('info', `Team "${team?.name}" deleted`);
|
|
471
582
|
} catch (err) {
|
|
@@ -483,7 +594,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
483
594
|
|
|
484
595
|
async renameTeam(id, name) {
|
|
485
596
|
try {
|
|
486
|
-
const team = await api.patch(`/teams/${id}`, { name });
|
|
597
|
+
const team = await api.patch(`/teams/${encodeURIComponent(id)}`, { name });
|
|
487
598
|
set((s) => ({ teams: s.teams.map((t) => (t.id === id ? team : t)) }));
|
|
488
599
|
return team;
|
|
489
600
|
} catch (err) {
|
|
@@ -510,6 +621,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
510
621
|
},
|
|
511
622
|
toggleCommandPalette() { set((s) => ({ commandPaletteOpen: !s.commandPaletteOpen })); },
|
|
512
623
|
toggleQuickConnect() { set((s) => ({ quickConnectOpen: !s.quickConnectOpen })); },
|
|
624
|
+
setUpgradeModalOpen: (open) => set({ upgradeModalOpen: open }),
|
|
513
625
|
|
|
514
626
|
setDetailPanelWidth(w) {
|
|
515
627
|
set({ detailPanelWidth: w });
|
|
@@ -552,6 +664,21 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
552
664
|
marketplaceAuthenticated: data.authenticated || false,
|
|
553
665
|
marketplaceUser: data.user || null,
|
|
554
666
|
});
|
|
667
|
+
try {
|
|
668
|
+
const edition = await api.get('/edition');
|
|
669
|
+
set({
|
|
670
|
+
edition: edition.edition || 'community',
|
|
671
|
+
subscription: {
|
|
672
|
+
plan: edition.plan || 'community',
|
|
673
|
+
status: edition.status || (edition.subscriptionActive ? 'active' : 'none'),
|
|
674
|
+
active: edition.subscriptionActive === true,
|
|
675
|
+
features: edition.features || [],
|
|
676
|
+
seats: edition.seats || 1,
|
|
677
|
+
periodEnd: edition.periodEnd || null,
|
|
678
|
+
cancelAtPeriodEnd: edition.cancelAtPeriodEnd || false,
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
} catch { /* edition endpoint may not exist */ }
|
|
555
682
|
} catch {
|
|
556
683
|
set({ marketplaceAuthenticated: false, marketplaceUser: null });
|
|
557
684
|
}
|
|
@@ -569,6 +696,38 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
569
696
|
clearInterval(poll);
|
|
570
697
|
set({ marketplaceAuthenticated: true, marketplaceUser: status.user });
|
|
571
698
|
get().addToast('success', `Signed in as ${status.user?.displayName || status.user?.id || 'user'}`);
|
|
699
|
+
try {
|
|
700
|
+
const edition = await api.get('/edition');
|
|
701
|
+
set({
|
|
702
|
+
edition: edition.edition || 'community',
|
|
703
|
+
subscription: {
|
|
704
|
+
plan: edition.plan || 'community',
|
|
705
|
+
status: edition.status || (edition.subscriptionActive ? 'active' : 'none'),
|
|
706
|
+
active: edition.subscriptionActive === true,
|
|
707
|
+
features: edition.features || [],
|
|
708
|
+
seats: edition.seats || 1,
|
|
709
|
+
periodEnd: edition.periodEnd || null,
|
|
710
|
+
cancelAtPeriodEnd: edition.cancelAtPeriodEnd || false,
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
} catch { /* edition endpoint may not exist */ }
|
|
714
|
+
setTimeout(async () => {
|
|
715
|
+
try {
|
|
716
|
+
const e = await api.get('/edition');
|
|
717
|
+
set({
|
|
718
|
+
edition: e.edition || 'community',
|
|
719
|
+
subscription: {
|
|
720
|
+
plan: e.plan || 'community',
|
|
721
|
+
status: e.status || (e.subscriptionActive ? 'active' : 'none'),
|
|
722
|
+
active: e.subscriptionActive === true,
|
|
723
|
+
features: e.features || [],
|
|
724
|
+
seats: e.seats || 1,
|
|
725
|
+
periodEnd: e.periodEnd || null,
|
|
726
|
+
cancelAtPeriodEnd: e.cancelAtPeriodEnd || false,
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
} catch { /* delayed re-fetch may fail */ }
|
|
730
|
+
}, 2000);
|
|
572
731
|
}
|
|
573
732
|
} catch { /* keep polling */ }
|
|
574
733
|
}, 2000);
|
|
@@ -600,6 +759,65 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
600
759
|
}
|
|
601
760
|
},
|
|
602
761
|
|
|
762
|
+
// ── Subscription ────────────────────────────────────────────
|
|
763
|
+
|
|
764
|
+
async fetchSubscriptionPlans() {
|
|
765
|
+
return api.get('/subscription/plans');
|
|
766
|
+
},
|
|
767
|
+
|
|
768
|
+
async startCheckout(priceId) {
|
|
769
|
+
try {
|
|
770
|
+
const data = await api.post('/subscription/checkout', { priceId });
|
|
771
|
+
if (data.url) {
|
|
772
|
+
if (window.groove?.openExternal) {
|
|
773
|
+
window.groove.openExternal(data.url);
|
|
774
|
+
} else {
|
|
775
|
+
window.open(data.url, '_blank');
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return data;
|
|
779
|
+
} catch (err) {
|
|
780
|
+
if (err.status === 401 || err.message?.includes('Not authenticated')) {
|
|
781
|
+
get().addToast('info', 'Please sign in to subscribe');
|
|
782
|
+
get().marketplaceLogin();
|
|
783
|
+
} else if (err.status === 409) {
|
|
784
|
+
get().addToast('info', 'Already subscribed', 'Use Manage Subscription to switch plans');
|
|
785
|
+
} else {
|
|
786
|
+
get().addToast('error', 'Checkout failed', err.message);
|
|
787
|
+
}
|
|
788
|
+
throw err;
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
|
|
792
|
+
async openPortal() {
|
|
793
|
+
try {
|
|
794
|
+
const data = await api.post('/subscription/portal');
|
|
795
|
+
if (data.url) {
|
|
796
|
+
if (window.groove?.openExternal) {
|
|
797
|
+
window.groove.openExternal(data.url);
|
|
798
|
+
} else {
|
|
799
|
+
window.open(data.url, '_blank');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return data;
|
|
803
|
+
} catch (err) {
|
|
804
|
+
get().addToast('error', 'Portal failed', err.message);
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
|
|
809
|
+
async updateSeats(seats) {
|
|
810
|
+
try {
|
|
811
|
+
const data = await api.patch('/subscription', { seats });
|
|
812
|
+
set({ subscription: { ...get().subscription, ...data } });
|
|
813
|
+
get().addToast('success', `Updated to ${seats} seat${seats !== 1 ? 's' : ''}`);
|
|
814
|
+
return data;
|
|
815
|
+
} catch (err) {
|
|
816
|
+
get().addToast('error', 'Seat update failed', err.message);
|
|
817
|
+
throw err;
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
|
|
603
821
|
// ── Approvals ──────────────────────────────────────────────
|
|
604
822
|
|
|
605
823
|
async fetchApprovals() {
|
|
@@ -614,7 +832,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
614
832
|
|
|
615
833
|
async approveRequest(id) {
|
|
616
834
|
try {
|
|
617
|
-
await api.post(`/approvals/${id}/approve`);
|
|
835
|
+
await api.post(`/approvals/${encodeURIComponent(id)}/approve`);
|
|
618
836
|
set((s) => ({ pendingApprovals: s.pendingApprovals.filter((a) => a.id !== id) }));
|
|
619
837
|
get().addToast('success', 'Approved');
|
|
620
838
|
} catch (err) {
|
|
@@ -624,7 +842,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
624
842
|
|
|
625
843
|
async rejectRequest(id, reason = '') {
|
|
626
844
|
try {
|
|
627
|
-
await api.post(`/approvals/${id}/reject`, { reason });
|
|
845
|
+
await api.post(`/approvals/${encodeURIComponent(id)}/reject`, { reason });
|
|
628
846
|
set((s) => ({ pendingApprovals: s.pendingApprovals.filter((a) => a.id !== id) }));
|
|
629
847
|
get().addToast('info', 'Rejected');
|
|
630
848
|
} catch (err) {
|
|
@@ -652,19 +870,26 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
652
870
|
const allExist = phase1Roles.every((role) => teamAgents.some((a) => a.role === role));
|
|
653
871
|
|
|
654
872
|
if (allExist && phase1Roles.length > 0) {
|
|
655
|
-
//
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
873
|
+
// Guard: skip if already delegating for this team (poll race)
|
|
874
|
+
if (get()._delegatingTeamIds.has(teamId)) return;
|
|
875
|
+
set((s) => ({ recommendedTeam: null, _delegatingTeamIds: new Set([...s._delegatingTeamIds, teamId]) }));
|
|
876
|
+
try {
|
|
877
|
+
const result = await api.post('/recommended-team/launch', { teamId });
|
|
878
|
+
const agents = result.agents || [];
|
|
879
|
+
const names = agents.map((a) => a.name).join(', ') || '';
|
|
880
|
+
get().addToast('success', 'Planner delegated work', names ? `→ ${names}` : undefined);
|
|
881
|
+
if (agents.length > 0) {
|
|
882
|
+
set((s) => ({
|
|
883
|
+
thinkingAgents: new Set([...s.thinkingAgents, ...agents.map((a) => a.id)]),
|
|
884
|
+
}));
|
|
885
|
+
}
|
|
886
|
+
} finally {
|
|
887
|
+
set((s) => {
|
|
888
|
+
const next = new Set(s._delegatingTeamIds);
|
|
889
|
+
next.delete(teamId);
|
|
890
|
+
return { _delegatingTeamIds: next };
|
|
891
|
+
});
|
|
666
892
|
}
|
|
667
|
-
api.post('/cleanup').catch(() => {});
|
|
668
893
|
return;
|
|
669
894
|
}
|
|
670
895
|
}
|
|
@@ -729,12 +954,12 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
729
954
|
},
|
|
730
955
|
|
|
731
956
|
async softRemoveRepo(importId) {
|
|
732
|
-
await api.delete(
|
|
957
|
+
await api.delete(`/repos/${encodeURIComponent(importId)}/remove`);
|
|
733
958
|
get().fetchImportedRepos();
|
|
734
959
|
},
|
|
735
960
|
|
|
736
961
|
async hardNukeRepo(importId, deleteFiles = true) {
|
|
737
|
-
await api.delete(
|
|
962
|
+
await api.delete(`/repos/${encodeURIComponent(importId)}/nuke?deleteFiles=${deleteFiles}`);
|
|
738
963
|
get().fetchImportedRepos();
|
|
739
964
|
},
|
|
740
965
|
|
|
@@ -754,22 +979,22 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
754
979
|
},
|
|
755
980
|
|
|
756
981
|
async updateTunnel(id, config) {
|
|
757
|
-
const result = await api.patch(
|
|
982
|
+
const result = await api.patch(`/tunnels/${encodeURIComponent(id)}`, config);
|
|
758
983
|
get().fetchTunnels();
|
|
759
984
|
return result;
|
|
760
985
|
},
|
|
761
986
|
|
|
762
987
|
async deleteTunnel(id) {
|
|
763
|
-
await api.delete(
|
|
988
|
+
await api.delete(`/tunnels/${encodeURIComponent(id)}`);
|
|
764
989
|
get().fetchTunnels();
|
|
765
990
|
},
|
|
766
991
|
|
|
767
992
|
async testTunnel(id) {
|
|
768
|
-
return api.post(
|
|
993
|
+
return api.post(`/tunnels/${encodeURIComponent(id)}/test`);
|
|
769
994
|
},
|
|
770
995
|
|
|
771
996
|
async connectTunnel(id) {
|
|
772
|
-
const result = await api.post(
|
|
997
|
+
const result = await api.post(`/tunnels/${encodeURIComponent(id)}/connect`);
|
|
773
998
|
set({ activeTunnelId: id });
|
|
774
999
|
get().fetchTunnels();
|
|
775
1000
|
if (result.url) window.open(result.url, '_blank');
|
|
@@ -777,17 +1002,17 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
777
1002
|
},
|
|
778
1003
|
|
|
779
1004
|
async disconnectTunnel(id) {
|
|
780
|
-
await api.post(
|
|
1005
|
+
await api.post(`/tunnels/${encodeURIComponent(id)}/disconnect`);
|
|
781
1006
|
set({ activeTunnelId: null });
|
|
782
1007
|
get().fetchTunnels();
|
|
783
1008
|
},
|
|
784
1009
|
|
|
785
1010
|
async installTunnel(id) {
|
|
786
|
-
return api.post(
|
|
1011
|
+
return api.post(`/tunnels/${encodeURIComponent(id)}/install`);
|
|
787
1012
|
},
|
|
788
1013
|
|
|
789
1014
|
async startTunnel(id) {
|
|
790
|
-
return api.post(
|
|
1015
|
+
return api.post(`/tunnels/${encodeURIComponent(id)}/start`);
|
|
791
1016
|
},
|
|
792
1017
|
|
|
793
1018
|
// ── Journalist ────────────────────────────────────────────
|
|
@@ -828,7 +1053,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
828
1053
|
|
|
829
1054
|
async killAgent(id, purge = false) {
|
|
830
1055
|
try {
|
|
831
|
-
await api.delete(`/agents/${id}?purge=${purge}`);
|
|
1056
|
+
await api.delete(`/agents/${encodeURIComponent(id)}?purge=${purge}`);
|
|
832
1057
|
} catch (err) {
|
|
833
1058
|
get().addToast('error', 'Kill failed', err.message);
|
|
834
1059
|
}
|
|
@@ -836,7 +1061,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
836
1061
|
|
|
837
1062
|
async rotateAgent(id) {
|
|
838
1063
|
try {
|
|
839
|
-
return await api.post(`/agents/${id}/rotate`);
|
|
1064
|
+
return await api.post(`/agents/${encodeURIComponent(id)}/rotate`);
|
|
840
1065
|
} catch (err) {
|
|
841
1066
|
get().addToast('error', 'Rotation failed', err.message);
|
|
842
1067
|
throw err;
|
|
@@ -847,6 +1072,48 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
847
1072
|
return api.get('/providers');
|
|
848
1073
|
},
|
|
849
1074
|
|
|
1075
|
+
// ── Onboarding ────────────────────────────────────────────
|
|
1076
|
+
|
|
1077
|
+
async fetchOnboardingStatus() {
|
|
1078
|
+
try {
|
|
1079
|
+
const data = await api.get('/onboarding/status');
|
|
1080
|
+
if (data?.complete) {
|
|
1081
|
+
set({ onboardingComplete: true });
|
|
1082
|
+
localStorage.setItem('groove:onboardingComplete', 'true');
|
|
1083
|
+
}
|
|
1084
|
+
return data;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
|
|
1090
|
+
dismissOnboarding() {
|
|
1091
|
+
set({ onboardingComplete: true });
|
|
1092
|
+
localStorage.setItem('groove:onboardingComplete', 'true');
|
|
1093
|
+
api.post('/onboarding/dismiss').catch(() => {});
|
|
1094
|
+
},
|
|
1095
|
+
|
|
1096
|
+
async installProvider(providerId) {
|
|
1097
|
+
try {
|
|
1098
|
+
const data = await api.post('/onboarding/install-provider', { provider: providerId });
|
|
1099
|
+
get().addToast('success', `${providerId} installed`);
|
|
1100
|
+
return data;
|
|
1101
|
+
} catch (err) {
|
|
1102
|
+
get().addToast('error', `Install failed: ${providerId}`, err.message);
|
|
1103
|
+
throw err;
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
|
|
1107
|
+
async setDefaultProvider(provider, model) {
|
|
1108
|
+
try {
|
|
1109
|
+
await api.post('/onboarding/set-default', { provider, model });
|
|
1110
|
+
get().addToast('success', `Default set to ${provider} (${model})`);
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
get().addToast('error', 'Failed to set default', err.message);
|
|
1113
|
+
throw err;
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
|
|
850
1117
|
// ── Chat ──────────────────────────────────────────────────
|
|
851
1118
|
|
|
852
1119
|
addChatMessage(agentId, from, text, isQuery = false) {
|
|
@@ -864,7 +1131,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
864
1131
|
|
|
865
1132
|
async stopAgent(id) {
|
|
866
1133
|
try {
|
|
867
|
-
await api.post(`/agents/${id}/stop`);
|
|
1134
|
+
await api.post(`/agents/${encodeURIComponent(id)}/stop`);
|
|
868
1135
|
// Clear thinking indicator
|
|
869
1136
|
set((s) => {
|
|
870
1137
|
const next = new Set(s.thinkingAgents);
|
|
@@ -878,41 +1145,24 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
878
1145
|
},
|
|
879
1146
|
|
|
880
1147
|
async instructAgent(id, message) {
|
|
881
|
-
|
|
882
|
-
|
|
1148
|
+
get().addChatMessage(id, 'user', message, false);
|
|
1149
|
+
set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, id]) }));
|
|
1150
|
+
try {
|
|
1151
|
+
const data = await api.post(`/agents/${encodeURIComponent(id)}/instruct`, { message });
|
|
883
1152
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
get().addChatMessage(id, 'user', message, false);
|
|
887
|
-
try {
|
|
888
|
-
const data = await api.post(`/agents/${id}/query`, { message });
|
|
889
|
-
// Agent loop agents: response comes via WebSocket, show thinking indicator
|
|
890
|
-
if (data.status === 'pending' || data.response === 'Message sent to agent') {
|
|
891
|
-
set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, id]) }));
|
|
892
|
-
return data;
|
|
893
|
-
}
|
|
894
|
-
get().addChatMessage(id, 'agent', data.response);
|
|
1153
|
+
// Agent loop: message sent directly to running agent — response comes via WebSocket
|
|
1154
|
+
if (data.status === 'message_sent') {
|
|
895
1155
|
return data;
|
|
896
|
-
} catch (err) {
|
|
897
|
-
get().addChatMessage(id, 'system', `failed: ${err.message}`);
|
|
898
|
-
throw err;
|
|
899
1156
|
}
|
|
900
|
-
}
|
|
901
1157
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
// Show thinking indicator immediately — stays until first WebSocket output
|
|
905
|
-
set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, id]) }));
|
|
906
|
-
try {
|
|
907
|
-
const newAgent = await api.post(`/agents/${id}/instruct`, { message });
|
|
908
|
-
// Carry history + thinking state to new agent ID
|
|
1158
|
+
// CLI agent: was stopped + resumed/rotated — transfer state to new agent ID
|
|
1159
|
+
const newAgent = data;
|
|
909
1160
|
for (const key of ['chatHistory', 'activityLog', 'tokenTimeline']) {
|
|
910
1161
|
const old = get()[key][id];
|
|
911
1162
|
if (old?.length) {
|
|
912
1163
|
set((s) => ({ [key]: { ...s[key], [newAgent.id]: [...old] } }));
|
|
913
1164
|
}
|
|
914
1165
|
}
|
|
915
|
-
// Transfer thinking indicator to the new agent
|
|
916
1166
|
set((s) => {
|
|
917
1167
|
const next = new Set(s.thinkingAgents);
|
|
918
1168
|
next.delete(id);
|
|
@@ -924,7 +1174,6 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
924
1174
|
get().selectAgent(newAgent.id);
|
|
925
1175
|
return newAgent;
|
|
926
1176
|
} catch (err) {
|
|
927
|
-
// Clear thinking indicator on failure
|
|
928
1177
|
set((s) => {
|
|
929
1178
|
const next = new Set(s.thinkingAgents);
|
|
930
1179
|
next.delete(id);
|
|
@@ -938,7 +1187,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
938
1187
|
async queryAgent(id, message) {
|
|
939
1188
|
get().addChatMessage(id, 'user', message, true);
|
|
940
1189
|
try {
|
|
941
|
-
const data = await api.post(`/agents/${id}/query`, { message });
|
|
1190
|
+
const data = await api.post(`/agents/${encodeURIComponent(id)}/query`, { message });
|
|
942
1191
|
get().addChatMessage(id, 'agent', data.response);
|
|
943
1192
|
return data;
|
|
944
1193
|
} catch (err) {
|
|
@@ -998,6 +1247,11 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
998
1247
|
|
|
999
1248
|
setActiveFile(path) { set({ editorActiveFile: path }); },
|
|
1000
1249
|
|
|
1250
|
+
setEditorSidebarWidth(width) {
|
|
1251
|
+
set({ editorSidebarWidth: width });
|
|
1252
|
+
localStorage.setItem('groove:editorSidebarWidth', String(width));
|
|
1253
|
+
},
|
|
1254
|
+
|
|
1001
1255
|
updateFileContent(path, content) {
|
|
1002
1256
|
set((s) => ({ editorFiles: { ...s.editorFiles, [path]: { ...s.editorFiles[path], content } } }));
|
|
1003
1257
|
},
|
|
@@ -1081,6 +1335,67 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1081
1335
|
}
|
|
1082
1336
|
},
|
|
1083
1337
|
|
|
1338
|
+
// ── Federation ────────────────────────────────────────────
|
|
1339
|
+
|
|
1340
|
+
async fetchFederationStatus() {
|
|
1341
|
+
try {
|
|
1342
|
+
const data = await api.get('/federation');
|
|
1343
|
+
set((s) => ({
|
|
1344
|
+
federation: {
|
|
1345
|
+
...s.federation,
|
|
1346
|
+
peers: data.peers || [],
|
|
1347
|
+
whitelist: data.whitelist || [],
|
|
1348
|
+
connections: data.connections || [],
|
|
1349
|
+
ambassadors: data.ambassadors?.ambassadors || data.ambassadors || [],
|
|
1350
|
+
},
|
|
1351
|
+
}));
|
|
1352
|
+
return data;
|
|
1353
|
+
} catch { return null; }
|
|
1354
|
+
},
|
|
1355
|
+
|
|
1356
|
+
async addToWhitelist(ip, port = 31415, name) {
|
|
1357
|
+
try {
|
|
1358
|
+
await api.post('/federation/whitelist', { ip, port, ...(name && { name }) });
|
|
1359
|
+
get().addToast('success', `Added ${ip} to whitelist`);
|
|
1360
|
+
get().fetchFederationStatus();
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
get().addToast('error', 'Whitelist failed', err.message);
|
|
1363
|
+
throw err;
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
|
|
1367
|
+
async removeFromWhitelist(ip) {
|
|
1368
|
+
try {
|
|
1369
|
+
await api.delete(`/federation/whitelist/${encodeURIComponent(ip)}`);
|
|
1370
|
+
get().addToast('info', `Removed ${ip}`);
|
|
1371
|
+
get().fetchFederationStatus();
|
|
1372
|
+
} catch (err) {
|
|
1373
|
+
get().addToast('error', 'Remove failed', err.message);
|
|
1374
|
+
}
|
|
1375
|
+
},
|
|
1376
|
+
|
|
1377
|
+
setSelectedPeer(peerId) {
|
|
1378
|
+
set((s) => ({ federation: { ...s.federation, selectedPeerId: peerId } }));
|
|
1379
|
+
},
|
|
1380
|
+
|
|
1381
|
+
async fetchPouchLog(peerId) {
|
|
1382
|
+
try {
|
|
1383
|
+
const data = await api.get(`/federation/pouch/log${peerId ? `?peerId=${encodeURIComponent(peerId)}` : ''}`);
|
|
1384
|
+
set((s) => ({ federation: { ...s.federation, pouchLog: data || [] } }));
|
|
1385
|
+
} catch { /* ignore */ }
|
|
1386
|
+
},
|
|
1387
|
+
|
|
1388
|
+
async sendPouch(peerId, contract) {
|
|
1389
|
+
try {
|
|
1390
|
+
const result = await api.post('/federation/pouch/send', { peerId, contract });
|
|
1391
|
+
get().addToast('success', 'Pouch sent');
|
|
1392
|
+
return result;
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
get().addToast('error', 'Pouch send failed', err.message);
|
|
1395
|
+
throw err;
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
|
|
1084
1399
|
async renameFile(oldPath, newPath) {
|
|
1085
1400
|
try {
|
|
1086
1401
|
await api.post('/files/rename', { oldPath, newPath });
|