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.
Files changed (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. package/test.py +0 -571
@@ -0,0 +1,10 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ export function loadJSON(key, fallback = {}) {
4
+ try { return JSON.parse(localStorage.getItem(key) || JSON.stringify(fallback)); }
5
+ catch { return fallback; }
6
+ }
7
+
8
+ export function persistJSON(key, value) {
9
+ try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* quota */ }
10
+ }
@@ -0,0 +1,452 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { api } from '../../lib/api';
4
+ import { loadJSON, persistJSON } from '../helpers.js';
5
+
6
+ export const createAgentsSlice = (set, get) => ({
7
+ // ── Agent data ────────────────────────────────────────────
8
+ agents: [],
9
+ activityLog: loadJSON('groove:activityLog'),
10
+ chatHistory: loadJSON('groove:chatHistory'),
11
+ chatInputs: {}, // Per-agent draft input text — persists across tab switches
12
+ tokenTimeline: {},
13
+
14
+ // Track which agents are thinking (sent a message, waiting for response)
15
+ thinkingAgents: new Set(),
16
+
17
+ // ── Workspace Mode ────────────────────────────────────────
18
+ workspaceMode: localStorage.getItem('groove:workspaceMode') === 'true',
19
+ workspaceAgentId: null,
20
+ workspaceReviewMode: false,
21
+ workspaceReviewFiles: [],
22
+
23
+ // ── Keeper (tagged memory) ─────────────────────────────────
24
+ keeperItems: [],
25
+ keeperTree: [],
26
+ keeperEditing: null, // { tag, content, isNew, readOnly } — drives the editor modal
27
+ keeperInstructOpen: false,
28
+
29
+ // ── Agent Actions ─────────────────────────────────────────
30
+
31
+ selectAgent(id) {
32
+ const tid = get().activeTeamId;
33
+ const match = get().agents.find((a) => a.id === id);
34
+ if (tid && match && match.teamId && match.teamId !== tid) return;
35
+ const panel = { type: 'agent', agentId: id };
36
+ set((s) => ({ detailPanel: panel, teamDetailPanels: { ...s.teamDetailPanels, [tid]: panel } }));
37
+ },
38
+
39
+ async spawnAgent(config) {
40
+ try {
41
+ const teamId = get().activeTeamId;
42
+ const agent = await api.post('/agents', { ...config, teamId });
43
+ get().addToast('success', `Spawned ${agent.name}`);
44
+ return agent;
45
+ } catch (err) {
46
+ let detail = err.message;
47
+ if (detail?.includes('workingDir must be within project directory')) {
48
+ const projDir = get().projectDir || 'unknown';
49
+ const workDir = config.workingDir || 'default';
50
+ detail = `workingDir "${workDir}" is outside project directory "${projDir}". Change the project directory or pick a subfolder within it.`;
51
+ }
52
+ get().addToast('error', 'Spawn failed', detail);
53
+ throw err;
54
+ }
55
+ },
56
+
57
+ async killAgent(id, purge = false) {
58
+ try {
59
+ await api.delete(`/agents/${encodeURIComponent(id)}?purge=${purge}`);
60
+ if (purge) {
61
+ set((s) => {
62
+ const chatHistory = { ...s.chatHistory };
63
+ const activityLog = { ...s.activityLog };
64
+ const tokenTimeline = { ...s.tokenTimeline };
65
+ delete chatHistory[id];
66
+ delete activityLog[id];
67
+ delete tokenTimeline[id];
68
+ persistJSON('groove:chatHistory', chatHistory);
69
+ persistJSON('groove:activityLog', activityLog);
70
+ return { chatHistory, activityLog, tokenTimeline };
71
+ });
72
+ }
73
+ } catch (err) {
74
+ get().addToast('error', 'Kill failed', err.message);
75
+ }
76
+ },
77
+
78
+ async rotateAgent(id) {
79
+ try {
80
+ return await api.post(`/agents/${encodeURIComponent(id)}/rotate`);
81
+ } catch (err) {
82
+ get().addToast('error', 'Rotation failed', err.message);
83
+ throw err;
84
+ }
85
+ },
86
+
87
+ // ── Chat ──────────────────────────────────────────────────
88
+
89
+ addChatMessage(agentId, from, text, isQuery = false) {
90
+ set((s) => {
91
+ const history = { ...s.chatHistory };
92
+ if (!history[agentId]) history[agentId] = [];
93
+ history[agentId] = [...history[agentId].slice(-100), { from, text, timestamp: Date.now(), isQuery }];
94
+ persistJSON('groove:chatHistory', history);
95
+ return { chatHistory: history };
96
+ });
97
+ },
98
+
99
+ async stopAgent(id) {
100
+ try {
101
+ await api.post(`/agents/${encodeURIComponent(id)}/stop`);
102
+ // Clear thinking indicator
103
+ set((s) => {
104
+ const next = new Set(s.thinkingAgents);
105
+ next.delete(id);
106
+ return { thinkingAgents: next };
107
+ });
108
+ get().addToast('info', 'Stopped agent');
109
+ } catch (err) {
110
+ get().addToast('error', 'Stop failed', err.message);
111
+ }
112
+ },
113
+
114
+ async instructAgent(id, message) {
115
+ // ── Keeper command interception ─────────────────────────
116
+ const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
117
+ if (keeperCmd) {
118
+ const handled = await get()._handleKeeperCommand(id, message, keeperCmd[1].toLowerCase());
119
+ if (handled === true) return { status: 'keeper_handled' };
120
+ if (handled?.passthrough) {
121
+ message = handled.passthrough;
122
+ }
123
+ }
124
+
125
+ get().addChatMessage(id, 'user', message, false);
126
+ set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, id]) }));
127
+
128
+ // Auto-attach active file context when in workspace mode
129
+ let enriched = message;
130
+ if (get().workspaceMode && get().workspaceAgentId === id && get().editorActiveFile) {
131
+ const filePath = get().editorActiveFile;
132
+ enriched = `[Active file: ${filePath}]\n\n${message}`;
133
+ }
134
+
135
+ const snapshot = {
136
+ chatHistory: [...(get().chatHistory[id] || [])],
137
+ activityLog: [...(get().activityLog[id] || [])],
138
+ tokenTimeline: [...(get().tokenTimeline[id] || [])],
139
+ };
140
+
141
+ try {
142
+ const data = await api.post(`/agents/${encodeURIComponent(id)}/instruct`, { message: enriched });
143
+
144
+ if (data.status === 'message_sent') {
145
+ return data;
146
+ }
147
+ if (data.status === 'message_queued') {
148
+ set((s) => {
149
+ const next = new Set(s.thinkingAgents);
150
+ next.delete(id);
151
+ return { thinkingAgents: next };
152
+ });
153
+ return data;
154
+ }
155
+
156
+ // CLI agent: was stopped + resumed/rotated — transfer state to new agent ID
157
+ const newAgent = data;
158
+ for (const key of ['chatHistory', 'activityLog', 'tokenTimeline']) {
159
+ if (snapshot[key]?.length) {
160
+ set((s) => ({ [key]: { ...s[key], [newAgent.id]: [...snapshot[key]] } }));
161
+ }
162
+ }
163
+ set((s) => {
164
+ const next = new Set(s.thinkingAgents);
165
+ next.delete(id);
166
+ next.add(newAgent.id);
167
+ return { thinkingAgents: next };
168
+ });
169
+ if (get().chatHistory[newAgent.id]?.length) persistJSON('groove:chatHistory', get().chatHistory);
170
+ if (get().activityLog[newAgent.id]?.length) persistJSON('groove:activityLog', get().activityLog);
171
+ if (get().labAssistantAgentId === id) {
172
+ localStorage.setItem('groove:labAssistantAgentId', newAgent.id);
173
+ set({ labAssistantAgentId: newAgent.id });
174
+ } else {
175
+ get().selectAgent(newAgent.id);
176
+ }
177
+ return newAgent;
178
+ } catch (err) {
179
+ set((s) => {
180
+ const next = new Set(s.thinkingAgents);
181
+ next.delete(id);
182
+ return { thinkingAgents: next };
183
+ });
184
+ get().addChatMessage(id, 'system', `failed: ${err.message}`);
185
+ throw err;
186
+ }
187
+ },
188
+
189
+ async queryAgent(id, message) {
190
+ get().addChatMessage(id, 'user', message, true);
191
+ try {
192
+ const data = await api.post(`/agents/${encodeURIComponent(id)}/query`, { message });
193
+ get().addChatMessage(id, 'agent', data.response);
194
+ return data;
195
+ } catch (err) {
196
+ get().addChatMessage(id, 'system', `query failed: ${err.message}`);
197
+ throw err;
198
+ }
199
+ },
200
+
201
+ // ── Workspace Mode ────────────────────────────────────────
202
+
203
+ setWorkspaceMode(on) {
204
+ set({ workspaceMode: on });
205
+ localStorage.setItem('groove:workspaceMode', String(on));
206
+ if (on) {
207
+ const teamAgents = get().agents.filter((a) => a.teamId === get().activeTeamId);
208
+ const current = get().workspaceAgentId;
209
+ const belongsToTeam = current && teamAgents.some((a) => a.id === current);
210
+ if (!belongsToTeam) {
211
+ const selected = get().detailPanel?.type === 'agent' ? get().detailPanel.agentId : null;
212
+ const selectedInTeam = selected && teamAgents.some((a) => a.id === selected);
213
+ const running = teamAgents.find((a) => a.status === 'running');
214
+ set({ workspaceAgentId: (selectedInTeam ? selected : null) || running?.id || teamAgents[0]?.id || null });
215
+ }
216
+ const agentId = get().workspaceAgentId;
217
+ if (agentId) get().selectAgent(agentId);
218
+ }
219
+ },
220
+
221
+ setWorkspaceAgent(id) {
222
+ set({ workspaceAgentId: id });
223
+ if (id) get().selectAgent(id);
224
+ },
225
+
226
+ async toggleReviewMode() {
227
+ const st = get();
228
+ if (st.workspaceReviewMode) {
229
+ set({ workspaceReviewMode: false, workspaceReviewFiles: [] });
230
+ return;
231
+ }
232
+ const agentId = st.workspaceAgentId;
233
+ if (!agentId) return;
234
+ try {
235
+ const res = await api.get(`/agents/${agentId}/files-touched`);
236
+ const touched = res.data || [];
237
+ const files = touched
238
+ .filter((f) => f.writes > 0)
239
+ .map((f) => ({ path: f.path, status: 'pending', comment: '' }));
240
+ set({ workspaceReviewMode: true, workspaceReviewFiles: files });
241
+ } catch (err) {
242
+ console.error('Failed to fetch touched files for review:', err);
243
+ }
244
+ },
245
+
246
+ approveFile(path) {
247
+ set((s) => ({
248
+ workspaceReviewFiles: s.workspaceReviewFiles.map((f) =>
249
+ f.path === path ? { ...f, status: 'approved' } : f,
250
+ ),
251
+ }));
252
+ },
253
+
254
+ rejectFile(path) {
255
+ set((s) => ({
256
+ workspaceReviewFiles: s.workspaceReviewFiles.map((f) =>
257
+ f.path === path ? { ...f, status: 'rejected' } : f,
258
+ ),
259
+ }));
260
+ },
261
+
262
+ commentFile(path, comment) {
263
+ set((s) => ({
264
+ workspaceReviewFiles: s.workspaceReviewFiles.map((f) =>
265
+ f.path === path ? { ...f, comment } : f,
266
+ ),
267
+ }));
268
+ },
269
+
270
+ // ── Keeper (tagged memory) ────────────────────────────────
271
+
272
+ async fetchKeeperItems() {
273
+ try {
274
+ const data = await api.get('/keeper');
275
+ const treeData = await api.get('/keeper/tree');
276
+ set({ keeperItems: data.items || [], keeperTree: treeData.tree || [] });
277
+ } catch { /* ignore */ }
278
+ },
279
+
280
+ async saveKeeperItem(tag, content) {
281
+ try {
282
+ const item = await api.post('/keeper', { tag, content });
283
+ get().fetchKeeperItems();
284
+ get().addToast('success', `Saved #${item.tag}`);
285
+ return item;
286
+ } catch (err) {
287
+ get().addToast('error', 'Failed to save memory', err.message);
288
+ throw err;
289
+ }
290
+ },
291
+
292
+ async appendKeeperItem(tag, content) {
293
+ try {
294
+ const item = await api.post('/keeper/append', { tag, content });
295
+ get().fetchKeeperItems();
296
+ get().addToast('success', `Appended to #${item.tag}`);
297
+ return item;
298
+ } catch (err) {
299
+ get().addToast('error', 'Failed to append', err.message);
300
+ throw err;
301
+ }
302
+ },
303
+
304
+ async updateKeeperItem(tag, content) {
305
+ try {
306
+ const item = await api.patch(`/keeper/${tag}`, { content });
307
+ get().fetchKeeperItems();
308
+ get().addToast('success', `Updated #${item.tag}`);
309
+ return item;
310
+ } catch (err) {
311
+ get().addToast('error', 'Failed to update memory', err.message);
312
+ throw err;
313
+ }
314
+ },
315
+
316
+ async deleteKeeperItem(tag) {
317
+ try {
318
+ await api.delete(`/keeper/${tag}`);
319
+ get().fetchKeeperItems();
320
+ get().addToast('success', `Deleted #${tag}`);
321
+ } catch (err) {
322
+ get().addToast('error', 'Failed to delete memory', err.message);
323
+ }
324
+ },
325
+
326
+ async getKeeperItem(tag) {
327
+ try {
328
+ return await api.get(`/keeper/${tag}`);
329
+ } catch {
330
+ return null;
331
+ }
332
+ },
333
+
334
+ async searchKeeper(query) {
335
+ try {
336
+ const data = await api.get(`/keeper/search?q=${encodeURIComponent(query)}`);
337
+ return data.results || [];
338
+ } catch {
339
+ return [];
340
+ }
341
+ },
342
+
343
+ setKeeperEditing(editing) {
344
+ set({ keeperEditing: editing });
345
+ },
346
+
347
+ async _handleKeeperCommand(agentId, message, command) {
348
+ const rest = message.replace(/\[\w+[-\w]*\]/i, '').trim();
349
+ const tags = (rest.match(/#[\w/.-]+/g) || []).map(t => t.replace(/^#/, ''));
350
+
351
+ const addSystemMsg = (text) => {
352
+ get().addChatMessage(agentId, 'system', text);
353
+ };
354
+
355
+ try {
356
+ switch (command) {
357
+ case 'instruct': {
358
+ set({ keeperInstructOpen: true });
359
+ return true;
360
+ }
361
+
362
+ case 'save': {
363
+ if (tags.length === 0) { addSystemMsg('Usage: save #tag your message here'); return true; }
364
+ const content = rest.replace(/#[\w/.-]+/g, '').trim();
365
+ if (!content) { addSystemMsg('Usage: save #tag your message here'); return true; }
366
+ await get().saveKeeperItem(tags[0], content);
367
+ addSystemMsg(`Saved to #${tags[0]}`);
368
+ return { passthrough: content };
369
+ }
370
+
371
+ case 'append': {
372
+ if (tags.length === 0) { addSystemMsg('Usage: append #tag content to add'); return true; }
373
+ const content = rest.replace(/#[\w/.-]+/g, '').trim();
374
+ if (!content) { addSystemMsg('Usage: append #tag content to add'); return true; }
375
+ await get().appendKeeperItem(tags[0], content);
376
+ addSystemMsg(`Appended to #${tags[0]}`);
377
+ return { passthrough: content };
378
+ }
379
+
380
+ case 'update': {
381
+ if (tags.length === 0) { addSystemMsg('Usage: [update] #tag'); return true; }
382
+ get().addChatMessage(agentId, 'user', message, false);
383
+ const existing = await get().getKeeperItem(tags[0]);
384
+ set({ keeperEditing: { tag: tags[0], content: existing?.content || '', isNew: !existing } });
385
+ return true;
386
+ }
387
+
388
+ case 'delete': {
389
+ if (tags.length === 0) { addSystemMsg('Usage: [delete] #tag'); return true; }
390
+ get().addChatMessage(agentId, 'user', message, false);
391
+ await get().deleteKeeperItem(tags[0]);
392
+ addSystemMsg(`Deleted #${tags[0]}`);
393
+ return true;
394
+ }
395
+
396
+ case 'view': {
397
+ if (tags.length === 0) { addSystemMsg('Usage: [view] #tag'); return true; }
398
+ get().addChatMessage(agentId, 'user', message, false);
399
+ const item = await get().getKeeperItem(tags[0]);
400
+ if (item) {
401
+ set({ keeperEditing: { tag: tags[0], content: item.content, isNew: false, readOnly: true } });
402
+ } else {
403
+ addSystemMsg(`#${tags[0]} not found`);
404
+ }
405
+ return true;
406
+ }
407
+
408
+ case 'read': {
409
+ if (tags.length === 0) { addSystemMsg('Usage: [read] #tag1 #tag2 ...'); return true; }
410
+ get().addChatMessage(agentId, 'user', message, false);
411
+ const readBrief = await api.post('/keeper/pull', { tags });
412
+ if (readBrief?.brief) {
413
+ await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, {
414
+ message: `Here is context from my tagged memories:\n\n${readBrief.brief}`,
415
+ });
416
+ addSystemMsg(`Sent ${tags.map(t => '#' + t).join(', ')} to agent`);
417
+ } else {
418
+ addSystemMsg(`No memories found for ${tags.map(t => '#' + t).join(', ')}`);
419
+ }
420
+ return true;
421
+ }
422
+
423
+ case 'doc': {
424
+ if (tags.length === 0) { addSystemMsg('Usage: [doc] #tag'); return true; }
425
+ get().addChatMessage(agentId, 'user', message, false);
426
+ addSystemMsg(`Generating doc for #${tags[0]}...`);
427
+ const history = get().chatHistory[agentId] || [];
428
+ const result = await api.post('/keeper/doc', { tag: tags[0], chatHistory: history, agentId });
429
+ if (result?.content) {
430
+ addSystemMsg(`Doc #${tags[0]} generated (${result.size}B)`);
431
+ set({ keeperEditing: { tag: tags[0], content: result.content, isNew: false } });
432
+ }
433
+ return true;
434
+ }
435
+
436
+ case 'link': {
437
+ const linkMatch = rest.match(/^((?:#[\w/.-]+\s*)+)\s+(.+)$/);
438
+ if (!linkMatch || tags.length === 0) { addSystemMsg('Usage: [link] #tag path/to/doc'); return true; }
439
+ const docPath = linkMatch[2].trim();
440
+ get().addChatMessage(agentId, 'user', message, false);
441
+ await api.post('/keeper/link', { tag: tags[0], docPath });
442
+ addSystemMsg(`Linked #${tags[0]} → ${docPath}`);
443
+ return true;
444
+ }
445
+ }
446
+ } catch (err) {
447
+ addSystemMsg(`Keeper error: ${err.message}`);
448
+ return true;
449
+ }
450
+ return false;
451
+ },
452
+ });
@@ -0,0 +1,96 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { api } from '../../lib/api';
4
+
5
+ export const createAutomationsSlice = (set, get) => ({
6
+ automations: [],
7
+ automationWizardOpen: false,
8
+ editingAutomationId: null,
9
+ availableGateways: [],
10
+ availableIntegrations: [],
11
+
12
+ async fetchAutomations() {
13
+ try {
14
+ const data = await api.get('/schedules');
15
+ set({ automations: data.schedules || data || [] });
16
+ } catch { /* ignore */ }
17
+ },
18
+
19
+ async createAutomation(config) {
20
+ try {
21
+ await api.post('/schedules', config);
22
+ get().addToast('success', 'Automation created');
23
+ get().fetchAutomations();
24
+ set({ automationWizardOpen: false, editingAutomationId: null });
25
+ } catch (err) {
26
+ get().addToast('error', 'Failed to create automation', err.message);
27
+ }
28
+ },
29
+
30
+ async updateAutomation(id, updates) {
31
+ try {
32
+ await api.patch(`/schedules/${encodeURIComponent(id)}`, updates);
33
+ get().addToast('success', 'Automation updated');
34
+ get().fetchAutomations();
35
+ } catch (err) {
36
+ get().addToast('error', 'Failed to update automation', err.message);
37
+ }
38
+ },
39
+
40
+ async deleteAutomation(id) {
41
+ try {
42
+ await api.delete(`/schedules/${encodeURIComponent(id)}`);
43
+ get().addToast('info', 'Automation deleted');
44
+ get().fetchAutomations();
45
+ } catch (err) {
46
+ get().addToast('error', 'Failed to delete automation', err.message);
47
+ }
48
+ },
49
+
50
+ async toggleAutomation(id, currentEnabled) {
51
+ try {
52
+ const action = currentEnabled ? 'disable' : 'enable';
53
+ await api.post(`/schedules/${encodeURIComponent(id)}/${action}`);
54
+ get().fetchAutomations();
55
+ } catch (err) {
56
+ get().addToast('error', 'Failed to toggle automation', err.message);
57
+ }
58
+ },
59
+
60
+ async runAutomation(id) {
61
+ try {
62
+ await api.post(`/schedules/${encodeURIComponent(id)}/run`);
63
+ get().addToast('success', 'Automation triggered');
64
+ } catch (err) {
65
+ get().addToast('error', 'Failed to run automation', err.message);
66
+ }
67
+ },
68
+
69
+ async duplicateAutomation(id) {
70
+ try {
71
+ await api.post(`/schedules/${encodeURIComponent(id)}/duplicate`);
72
+ get().addToast('success', 'Automation duplicated');
73
+ get().fetchAutomations();
74
+ } catch (err) {
75
+ get().addToast('error', 'Failed to duplicate automation', err.message);
76
+ }
77
+ },
78
+
79
+ openAutomationWizard() { set({ automationWizardOpen: true }); },
80
+ closeAutomationWizard() { set({ automationWizardOpen: false, editingAutomationId: null }); },
81
+ setEditingAutomation(id) { set({ editingAutomationId: id }); },
82
+
83
+ async fetchGateways() {
84
+ try {
85
+ const data = await api.get('/gateways');
86
+ set({ availableGateways: data.gateways || data || [] });
87
+ } catch { /* ignore */ }
88
+ },
89
+
90
+ async fetchInstalledIntegrations() {
91
+ try {
92
+ const data = await api.get('/integrations/installed');
93
+ set({ availableIntegrations: data.integrations || data || [] });
94
+ } catch { /* ignore */ }
95
+ },
96
+ });