groove-dev 0.27.143 → 0.27.145

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 (251) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  5. package/node_modules/@groove-dev/daemon/src/conversations.js +18 -48
  6. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  7. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  8. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  9. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  10. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  11. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  12. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  13. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  14. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  15. package/node_modules/@groove-dev/daemon/src/routes/agents.js +812 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  22. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  23. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  24. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  25. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  26. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  27. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  29. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  30. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  31. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Bxc0gU06.js +1006 -0
  33. package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.css +1 -0
  34. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  35. package/node_modules/@groove-dev/gui/package.json +1 -1
  36. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  37. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +210 -112
  40. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  41. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
  42. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  43. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  44. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  45. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  46. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  47. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  48. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +2 -0
  49. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +68 -66
  50. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +4 -8
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  55. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  56. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  57. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  58. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  59. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  60. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +39 -31
  61. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  62. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  63. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +200 -18
  64. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
  65. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +335 -152
  66. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  67. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  68. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  69. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  70. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  71. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  72. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  73. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  74. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
  75. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  76. package/node_modules/@groove-dev/gui/src/lib/status.js +25 -24
  77. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  78. package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -3144
  79. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +459 -0
  81. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  82. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +226 -0
  83. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  84. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  85. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  86. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  87. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  88. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  89. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  90. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  91. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  92. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  93. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +54 -12
  94. package/node_modules/@groove-dev/gui/src/views/models.jsx +419 -496
  95. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  96. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  97. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  98. package/node_modules/axios/CHANGELOG.md +260 -0
  99. package/node_modules/axios/README.md +595 -223
  100. package/node_modules/axios/dist/axios.js +1460 -1090
  101. package/node_modules/axios/dist/axios.js.map +1 -1
  102. package/node_modules/axios/dist/axios.min.js +3 -3
  103. package/node_modules/axios/dist/axios.min.js.map +1 -1
  104. package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
  105. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  106. package/node_modules/axios/dist/esm/axios.js +1557 -1128
  107. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  108. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  109. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  110. package/node_modules/axios/dist/node/axios.cjs +1594 -1057
  111. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  112. package/node_modules/axios/index.d.cts +40 -41
  113. package/node_modules/axios/index.d.ts +151 -227
  114. package/node_modules/axios/index.js +2 -0
  115. package/node_modules/axios/lib/adapters/adapters.js +4 -2
  116. package/node_modules/axios/lib/adapters/fetch.js +147 -16
  117. package/node_modules/axios/lib/adapters/http.js +306 -58
  118. package/node_modules/axios/lib/adapters/xhr.js +6 -2
  119. package/node_modules/axios/lib/core/Axios.js +7 -3
  120. package/node_modules/axios/lib/core/AxiosError.js +120 -34
  121. package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
  122. package/node_modules/axios/lib/core/buildFullPath.js +1 -1
  123. package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
  124. package/node_modules/axios/lib/core/mergeConfig.js +21 -4
  125. package/node_modules/axios/lib/core/settle.js +7 -11
  126. package/node_modules/axios/lib/defaults/index.js +14 -9
  127. package/node_modules/axios/lib/env/data.js +1 -1
  128. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
  129. package/node_modules/axios/lib/helpers/buildURL.js +1 -1
  130. package/node_modules/axios/lib/helpers/cookies.js +14 -2
  131. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
  132. package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
  133. package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
  134. package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
  135. package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
  136. package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
  137. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
  138. package/node_modules/axios/lib/helpers/toFormData.js +10 -2
  139. package/node_modules/axios/lib/helpers/validator.js +3 -1
  140. package/node_modules/axios/lib/utils.js +33 -21
  141. package/node_modules/axios/package.json +17 -24
  142. package/node_modules/follow-redirects/README.md +7 -5
  143. package/node_modules/follow-redirects/index.js +24 -1
  144. package/node_modules/follow-redirects/package.json +1 -1
  145. package/package.json +1 -1
  146. package/packages/cli/package.json +1 -1
  147. package/packages/daemon/package.json +1 -1
  148. package/packages/daemon/src/api.js +1086 -6532
  149. package/packages/daemon/src/conversations.js +18 -48
  150. package/packages/daemon/src/gateways/manager.js +35 -1
  151. package/packages/daemon/src/index.js +3 -0
  152. package/packages/daemon/src/journalist.js +23 -13
  153. package/packages/daemon/src/mlx-server.js +365 -0
  154. package/packages/daemon/src/model-lab.js +308 -12
  155. package/packages/daemon/src/pm.js +1 -1
  156. package/packages/daemon/src/process.js +2 -2
  157. package/packages/daemon/src/providers/local.js +36 -8
  158. package/packages/daemon/src/registry.js +21 -5
  159. package/packages/daemon/src/routes/agents.js +812 -0
  160. package/packages/daemon/src/routes/coordination.js +318 -0
  161. package/packages/daemon/src/routes/files.js +751 -0
  162. package/packages/daemon/src/routes/integrations.js +485 -0
  163. package/packages/daemon/src/routes/network.js +1784 -0
  164. package/packages/daemon/src/routes/providers.js +755 -0
  165. package/packages/daemon/src/routes/schedules.js +110 -0
  166. package/packages/daemon/src/routes/teams.js +650 -0
  167. package/packages/daemon/src/scheduler.js +456 -24
  168. package/packages/daemon/src/teams.js +1 -1
  169. package/packages/daemon/src/validate.js +38 -1
  170. package/packages/daemon/templates/mlx-setup.json +12 -0
  171. package/packages/daemon/templates/tgi-setup.json +1 -1
  172. package/packages/daemon/templates/vllm-setup.json +1 -1
  173. package/packages/gui/dist/assets/index-Bxc0gU06.js +1006 -0
  174. package/packages/gui/dist/assets/index-C0pztKBn.css +1 -0
  175. package/packages/gui/dist/index.html +2 -2
  176. package/packages/gui/package.json +1 -1
  177. package/packages/gui/src/{app.jsx → App.jsx} +0 -2
  178. package/packages/gui/src/app.css +35 -0
  179. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  180. package/packages/gui/src/components/agents/agent-feed.jsx +210 -112
  181. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  182. package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
  183. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  184. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  185. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  186. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  187. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  188. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  189. package/packages/gui/src/components/chat/chat-header.jsx +2 -0
  190. package/packages/gui/src/components/chat/chat-input.jsx +68 -66
  191. package/packages/gui/src/components/chat/chat-view.jsx +4 -8
  192. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  193. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  194. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  195. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  196. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  197. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  198. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  199. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  200. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  201. package/packages/gui/src/components/lab/chat-playground.jsx +39 -31
  202. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  203. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  204. package/packages/gui/src/components/lab/parameter-panel.jsx +200 -18
  205. package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
  206. package/packages/gui/src/components/lab/runtime-config.jsx +335 -152
  207. package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  208. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  209. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  210. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  211. package/packages/gui/src/components/network/network-health.jsx +2 -2
  212. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  213. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  214. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  215. package/packages/gui/src/components/ui/slider.jsx +8 -8
  216. package/packages/gui/src/lib/cron.js +64 -0
  217. package/packages/gui/src/lib/status.js +25 -24
  218. package/packages/gui/src/lib/theme-hex.js +1 -0
  219. package/packages/gui/src/stores/groove.js +51 -3144
  220. package/packages/gui/src/stores/helpers.js +10 -0
  221. package/packages/gui/src/stores/slices/agents-slice.js +459 -0
  222. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  223. package/packages/gui/src/stores/slices/chat-slice.js +226 -0
  224. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  225. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  226. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  227. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  228. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  229. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  230. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  231. package/packages/gui/src/views/agents.jsx +5 -5
  232. package/packages/gui/src/views/dashboard.jsx +12 -13
  233. package/packages/gui/src/views/marketplace.jsx +191 -3
  234. package/packages/gui/src/views/model-lab.jsx +54 -12
  235. package/packages/gui/src/views/models.jsx +419 -496
  236. package/packages/gui/src/views/network.jsx +3 -3
  237. package/packages/gui/src/views/settings.jsx +81 -94
  238. package/packages/gui/src/views/teams.jsx +40 -483
  239. package/SECURITY_SWEEP.md +0 -228
  240. package/TRAINING_DATA_v4.md +0 -6
  241. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  242. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  243. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  244. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  245. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  246. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  247. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  248. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  249. package/packages/gui/src/views/preview.jsx +0 -6
  250. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  251. package/test.py +0 -571
@@ -0,0 +1,485 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { resolve } from 'path';
3
+ import { mkdirSync, writeFileSync } from 'fs';
4
+
5
+ function _isScheduledAgent(daemon, integrationId) {
6
+ if (!daemon.scheduler) return false;
7
+ for (const [, runInfo] of daemon.scheduler.runningAgents) {
8
+ const ids = typeof runInfo === 'string' ? [runInfo] : runInfo?.agentIds || [];
9
+ for (const aid of ids) {
10
+ const agent = daemon.registry.get(aid);
11
+ if (agent && agent.integrations?.includes(integrationId)) return true;
12
+ }
13
+ }
14
+ return false;
15
+ }
16
+
17
+ export function registerIntegrationRoutes(app, daemon) {
18
+
19
+ // --- Skills Marketplace ---
20
+
21
+ app.get('/api/skills/registry', async (req, res) => {
22
+ const skills = await daemon.skills.getRegistry({
23
+ search: req.query.search || '',
24
+ category: req.query.category || 'all',
25
+ });
26
+ res.json({
27
+ skills,
28
+ categories: daemon.skills.getCategories(),
29
+ });
30
+ });
31
+
32
+ app.get('/api/skills/installed', (req, res) => {
33
+ res.json(daemon.skills.getInstalled());
34
+ });
35
+
36
+ app.post('/api/skills/:id/install', async (req, res) => {
37
+ try {
38
+ const result = await daemon.skills.install(req.params.id);
39
+ res.json(result);
40
+ } catch (err) {
41
+ res.status(400).json({ error: err.message });
42
+ }
43
+ });
44
+
45
+ app.delete('/api/skills/:id', (req, res) => {
46
+ try {
47
+ const result = daemon.skills.uninstall(req.params.id);
48
+ res.json(result);
49
+ } catch (err) {
50
+ res.status(400).json({ error: err.message });
51
+ }
52
+ });
53
+
54
+ app.post('/api/skills/:id/update', async (req, res) => {
55
+ try {
56
+ const result = await daemon.skills.update(req.params.id);
57
+ res.json(result);
58
+ } catch (err) {
59
+ res.status(400).json({ error: err.message });
60
+ }
61
+ });
62
+
63
+ app.post('/api/skills/:id/rate', async (req, res) => {
64
+ try {
65
+ const rating = parseInt(req.body?.rating, 10);
66
+ const result = await daemon.skills.rate(req.params.id, rating);
67
+ res.json(result);
68
+ } catch (err) {
69
+ res.status(400).json({ error: err.message });
70
+ }
71
+ });
72
+
73
+ // Import a local .md skill file
74
+ app.post('/api/skills/import', (req, res) => {
75
+ const { name, content } = req.body;
76
+ if (!content) return res.status(400).json({ error: 'content is required' });
77
+ if (!name) return res.status(400).json({ error: 'name is required' });
78
+
79
+ const id = name.toLowerCase().replace(/\.md$/i, '').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
80
+ if (!id) return res.status(400).json({ error: 'Invalid skill name' });
81
+
82
+ const skillDir = resolve(daemon.skills.skillsDir, id);
83
+ mkdirSync(skillDir, { recursive: true });
84
+ writeFileSync(resolve(skillDir, 'SKILL.md'), content);
85
+ daemon.audit.log('skill.import', { id, name });
86
+ res.json({ id, name, installed: true, source: 'local' });
87
+ });
88
+
89
+ app.get('/api/skills/:id/content', async (req, res) => {
90
+ try {
91
+ const result = await daemon.skills.getContentPreview(req.params.id);
92
+ res.json(result);
93
+ } catch (err) {
94
+ res.status(500).json({ error: err.message });
95
+ }
96
+ });
97
+
98
+ // --- Integrations ---
99
+
100
+ // Google OAuth routes MUST come before parameterized :id routes
101
+ // (Express matches in order — :id would swallow 'google-oauth')
102
+
103
+ app.get('/api/integrations/registry', async (req, res) => {
104
+ const integrations = await daemon.integrations.getRegistry({
105
+ search: req.query.search || '',
106
+ category: req.query.category || 'all',
107
+ });
108
+ res.json({
109
+ integrations,
110
+ categories: daemon.integrations.getCategories(),
111
+ });
112
+ });
113
+
114
+ app.get('/api/integrations/installed', (req, res) => {
115
+ res.json(daemon.integrations.getInstalled());
116
+ });
117
+
118
+ app.get('/api/integrations/google-oauth/status', (req, res) => {
119
+ res.json({ configured: daemon.integrations.isGoogleOAuthConfigured() });
120
+ });
121
+
122
+ app.post('/api/integrations/google-oauth/setup', (req, res) => {
123
+ try {
124
+ const { clientId, clientSecret } = req.body || {};
125
+ if (!clientId || !clientSecret) return res.status(400).json({ error: 'clientId and clientSecret are required' });
126
+ daemon.integrations.setCredential('google-oauth', 'GOOGLE_CLIENT_ID', clientId);
127
+ daemon.integrations.setCredential('google-oauth', 'GOOGLE_CLIENT_SECRET', clientSecret);
128
+ res.json({ ok: true });
129
+ } catch (err) {
130
+ res.status(400).json({ error: err.message });
131
+ }
132
+ });
133
+
134
+ app.get('/api/integrations/oauth/callback', async (req, res) => {
135
+ const wantsJson = req.query.format === 'json' || (req.headers.accept && req.headers.accept.includes('application/json'));
136
+ try {
137
+ const { code, state } = req.query;
138
+ if (!code || !state) {
139
+ if (wantsJson) return res.status(400).json({ error: 'Missing code or state parameter' });
140
+ return res.status(400).send('Missing code or state parameter');
141
+ }
142
+ const result = await daemon.integrations.handleOAuthCallback(code, state);
143
+ daemon.broadcast({ type: 'integration-oauth-complete', integrationIds: result.integrationIds });
144
+ if (wantsJson) return res.json({ ok: true });
145
+ res.send(`<!DOCTYPE html><html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e2127;color:#e6e6e6">
146
+ <div style="text-align:center">
147
+ <div style="font-size:48px;margin-bottom:16px">&#10003;</div>
148
+ <h2>Connected!</h2>
149
+ <p style="color:#7a8394">You can close this tab and return to Groove.</p>
150
+ <script>setTimeout(()=>window.close(),2000)</script>
151
+ </div>
152
+ </body></html>`);
153
+ } catch (err) {
154
+ if (wantsJson) return res.status(400).json({ error: err.message });
155
+ res.status(400).send(`<!DOCTYPE html><html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e2127;color:#e06c75">
156
+ <div style="text-align:center">
157
+ <h2>Connection Failed</h2>
158
+ <p>${err.message}</p>
159
+ <p style="color:#7a8394">Close this tab and try again in Groove.</p>
160
+ </div>
161
+ </body></html>`);
162
+ }
163
+ });
164
+
165
+ app.post('/api/integrations/google-workspace/oauth/start', (req, res) => {
166
+ try {
167
+ const { integrationIds } = req.body || {};
168
+ if (!integrationIds?.length) return res.status(400).json({ error: 'integrationIds required' });
169
+ const url = daemon.integrations.getGoogleWorkspaceOAuthUrl(integrationIds);
170
+ res.json({ url });
171
+ } catch (err) {
172
+ res.status(400).json({ error: err.message });
173
+ }
174
+ });
175
+
176
+ // Parameterized :id routes (after specific routes above)
177
+
178
+ app.post('/api/integrations/:id/authenticate', (req, res) => {
179
+ console.log(`[Groove:API] POST /api/integrations/${req.params.id}/authenticate`);
180
+ try {
181
+ const handle = daemon.integrations.authenticate(req.params.id);
182
+ console.log(`[Groove:API] Authenticate started, PID: ${handle.pid}`);
183
+ res.json({ ok: true, pid: handle.pid });
184
+ } catch (err) {
185
+ console.log(`[Groove:API] Authenticate error: ${err.message}`);
186
+ res.status(400).json({ error: err.message });
187
+ }
188
+ });
189
+
190
+ app.post('/api/integrations/:id/install', async (req, res) => {
191
+ try {
192
+ const result = await daemon.integrations.install(req.params.id);
193
+ res.json(result);
194
+ } catch (err) {
195
+ res.status(400).json({ error: err.message });
196
+ }
197
+ });
198
+
199
+ app.delete('/api/integrations/:id', async (req, res) => {
200
+ try {
201
+ const result = await daemon.integrations.uninstall(req.params.id);
202
+ res.json(result);
203
+ } catch (err) {
204
+ res.status(400).json({ error: err.message });
205
+ }
206
+ });
207
+
208
+ app.get('/api/integrations/:id/status', (req, res) => {
209
+ const status = daemon.integrations.getStatus(req.params.id);
210
+ if (!status) return res.status(404).json({ error: 'Integration not found' });
211
+ res.json(status);
212
+ });
213
+
214
+ app.post('/api/integrations/:id/credentials', (req, res) => {
215
+ try {
216
+ const { key, value } = req.body || {};
217
+ if (!key || !value) return res.status(400).json({ error: 'key and value are required' });
218
+ daemon.integrations.setCredential(req.params.id, key, value);
219
+ res.json({ ok: true });
220
+ } catch (err) {
221
+ res.status(400).json({ error: err.message });
222
+ }
223
+ });
224
+
225
+ app.delete('/api/integrations/:id/credentials/:key', (req, res) => {
226
+ try {
227
+ daemon.integrations.deleteCredential(req.params.id, req.params.key);
228
+ res.json({ ok: true });
229
+ } catch (err) {
230
+ res.status(400).json({ error: err.message });
231
+ }
232
+ });
233
+
234
+ app.post('/api/integrations/:id/oauth/start', (req, res) => {
235
+ try {
236
+ const url = daemon.integrations.getOAuthUrl(req.params.id);
237
+ res.json({ url });
238
+ } catch (err) {
239
+ res.status(400).json({ error: err.message });
240
+ }
241
+ });
242
+
243
+ // --- Integration Execution (provider-agnostic) ---
244
+
245
+ const _execRates = new Map();
246
+ const EXEC_RATE_LIMIT = 30;
247
+ const EXEC_RATE_WINDOW = 60_000;
248
+
249
+ app.post('/api/integrations/:id/exec', async (req, res) => {
250
+ try {
251
+ const { tool, params, approvalId, agent: agentId } = req.body || {};
252
+ if (!tool || typeof tool !== 'string') {
253
+ return res.status(400).json({ error: 'tool (string) is required' });
254
+ }
255
+ if (params !== undefined && (typeof params !== 'object' || Array.isArray(params))) {
256
+ return res.status(400).json({ error: 'params must be an object' });
257
+ }
258
+ const integrationId = req.params.id;
259
+ if (!daemon.integrations._isInstalled(integrationId)) {
260
+ return res.status(400).json({ error: 'Integration not installed' });
261
+ }
262
+
263
+ // Rate limiting — sliding window per integration
264
+ const now = Date.now();
265
+ let window = _execRates.get(integrationId) || [];
266
+ window = window.filter((t) => now - t < EXEC_RATE_WINDOW);
267
+ if (window.length >= EXEC_RATE_LIMIT) {
268
+ daemon.audit.log('integration.exec.rate_limited', { integrationId, tool, agentId });
269
+ return res.status(429).json({ error: `Rate limit exceeded (${EXEC_RATE_LIMIT}/min) for ${integrationId}` });
270
+ }
271
+ window.push(now);
272
+ _execRates.set(integrationId, window);
273
+
274
+ // Approval gate — dangerous tools require human approval (unless agent is set to auto)
275
+ const entry = daemon.integrations.registry.find((s) => s.id === integrationId);
276
+ const callingAgent = agentId ? daemon.registry.get(agentId) : null;
277
+ const autoApprove = callingAgent?.integrationApproval === 'auto';
278
+ const scheduledBypass = _isScheduledAgent(daemon, integrationId);
279
+ if (entry?.requiresApproval?.includes(tool) && !autoApprove && !scheduledBypass) {
280
+ if (approvalId) {
281
+ const approval = daemon.supervisor.getApproval(approvalId);
282
+ if (!approval) return res.status(404).json({ error: 'Approval not found' });
283
+ if (approval.status === 'rejected') {
284
+ return res.status(403).json({ error: 'Approval rejected', reason: approval.reason });
285
+ }
286
+ if (approval.status !== 'approved') {
287
+ return res.status(202).json({ requiresApproval: true, approvalId, status: 'pending', message: 'Waiting for human approval' });
288
+ }
289
+ } else {
290
+ const paramsSummary = params ? JSON.stringify(params).slice(0, 500) : '{}';
291
+ const approval = daemon.supervisor.requestApproval(agentId || null, {
292
+ type: 'integration_exec',
293
+ integrationId,
294
+ tool,
295
+ params: paramsSummary,
296
+ description: `${entry.name}: ${tool}`,
297
+ }, {
298
+ type: 'integration_exec',
299
+ integrationId,
300
+ tool,
301
+ params: params || {},
302
+ agentId: agentId || null,
303
+ });
304
+ daemon.audit.log('integration.exec.blocked', { integrationId, tool, approvalId: approval.id, agentId });
305
+ return res.status(202).json({
306
+ requiresApproval: true,
307
+ approvalId: approval.id,
308
+ message: `Tool "${tool}" requires approval. The user will be prompted automatically. You will receive the result once approved — do not retry.`,
309
+ });
310
+ }
311
+ }
312
+
313
+ const result = await daemon.mcpManager.execTool(integrationId, tool, params || {});
314
+ daemon.audit.log('integration.exec', { integrationId, tool, params: params ? JSON.stringify(params).slice(0, 200) : '{}', agentId });
315
+ res.json({ result });
316
+ } catch (err) {
317
+ res.status(400).json({ error: err.message });
318
+ }
319
+ });
320
+
321
+ app.get('/api/integrations/:id/tools', async (req, res) => {
322
+ try {
323
+ if (!daemon.integrations._isInstalled(req.params.id)) {
324
+ return res.status(400).json({ error: 'Integration not installed' });
325
+ }
326
+ const tools = await daemon.mcpManager.listTools(req.params.id);
327
+ res.json({ tools });
328
+ } catch (err) {
329
+ res.status(400).json({ error: err.message });
330
+ }
331
+ });
332
+
333
+ // --- Google Drive Upload (file → native Google Workspace format) ---
334
+
335
+ app.post('/api/integrations/google-drive/upload', async (req, res) => {
336
+ try {
337
+ const { filePath, name, folderId, convert, approvalId, agent: agentId } = req.body || {};
338
+ if (!filePath || typeof filePath !== 'string') {
339
+ return res.status(400).json({ error: 'filePath (string) is required' });
340
+ }
341
+
342
+ // Approval gate (unless agent is set to auto or scheduled)
343
+ const uploadAgent = agentId ? daemon.registry.get(agentId) : null;
344
+ const autoApproveUpload = uploadAgent?.integrationApproval === 'auto';
345
+ const scheduledUploadBypass = _isScheduledAgent(daemon, 'google-drive');
346
+ if (!autoApproveUpload && !scheduledUploadBypass) {
347
+ if (approvalId) {
348
+ const approval = daemon.supervisor.getApproval(approvalId);
349
+ if (!approval) return res.status(404).json({ error: 'Approval not found' });
350
+ if (approval.status === 'rejected') return res.status(403).json({ error: 'Approval rejected', reason: approval.reason });
351
+ if (approval.status !== 'approved') return res.status(202).json({ requiresApproval: true, approvalId, status: 'pending' });
352
+ } else {
353
+ const approval = daemon.supervisor.requestApproval(agentId || null, {
354
+ type: 'google_drive_upload',
355
+ filePath,
356
+ name: name || filePath.split('/').pop(),
357
+ description: `Upload to Google Drive: ${name || filePath.split('/').pop()}`,
358
+ }, {
359
+ type: 'google_drive_upload',
360
+ filePath,
361
+ name: name || filePath.split('/').pop(),
362
+ folderId: folderId || null,
363
+ convert: convert !== false,
364
+ agentId: agentId || null,
365
+ });
366
+ daemon.audit.log('integration.upload.blocked', { filePath, approvalId: approval.id, agentId });
367
+ return res.status(202).json({
368
+ requiresApproval: true,
369
+ approvalId: approval.id,
370
+ message: `Upload requires approval. The user will be prompted automatically. You will receive the result once approved — do not retry.`,
371
+ });
372
+ }
373
+ }
374
+
375
+ const result = await daemon.integrations.uploadToGoogleDrive(filePath, {
376
+ name, folderId, convert: convert !== false,
377
+ });
378
+
379
+ daemon.audit.log('integration.upload', { filePath, driveFileId: result.id, name: result.name, agentId });
380
+ res.json(result);
381
+ } catch (err) {
382
+ res.status(400).json({ error: err.message });
383
+ }
384
+ });
385
+
386
+ // --- Gateways (Telegram, Discord, Slack) ---
387
+
388
+ app.get('/api/gateways', (req, res) => {
389
+ res.json(daemon.gateways.list());
390
+ });
391
+
392
+ app.post('/api/gateways', async (req, res) => {
393
+ try {
394
+ const result = await daemon.gateways.create(req.body || {});
395
+ res.json(result);
396
+ } catch (err) {
397
+ res.status(400).json({ error: err.message });
398
+ }
399
+ });
400
+
401
+ app.get('/api/gateways/:id', (req, res) => {
402
+ const gw = daemon.gateways.get(req.params.id);
403
+ if (!gw) return res.status(404).json({ error: 'Gateway not found' });
404
+ res.json(gw);
405
+ });
406
+
407
+ app.patch('/api/gateways/:id', async (req, res) => {
408
+ try {
409
+ const result = await daemon.gateways.update(req.params.id, req.body || {});
410
+ res.json(result);
411
+ } catch (err) {
412
+ res.status(400).json({ error: err.message });
413
+ }
414
+ });
415
+
416
+ app.delete('/api/gateways/:id', async (req, res) => {
417
+ try {
418
+ await daemon.gateways.delete(req.params.id);
419
+ res.json({ ok: true });
420
+ } catch (err) {
421
+ res.status(400).json({ error: err.message });
422
+ }
423
+ });
424
+
425
+ app.post('/api/gateways/:id/test', async (req, res) => {
426
+ try {
427
+ const result = await daemon.gateways.test(req.params.id);
428
+ res.json(result);
429
+ } catch (err) {
430
+ res.status(400).json({ error: err.message });
431
+ }
432
+ });
433
+
434
+ app.post('/api/gateways/:id/connect', async (req, res) => {
435
+ try {
436
+ const result = await daemon.gateways.connect(req.params.id);
437
+ res.json(result);
438
+ } catch (err) {
439
+ res.status(400).json({ error: err.message });
440
+ }
441
+ });
442
+
443
+ app.post('/api/gateways/:id/disconnect', async (req, res) => {
444
+ try {
445
+ const result = await daemon.gateways.disconnect(req.params.id);
446
+ res.json(result);
447
+ } catch (err) {
448
+ res.status(400).json({ error: err.message });
449
+ }
450
+ });
451
+
452
+ app.post('/api/gateways/:id/credentials', (req, res) => {
453
+ try {
454
+ const { key, value } = req.body || {};
455
+ if (!key || !value) return res.status(400).json({ error: 'key and value are required' });
456
+ daemon.gateways.setCredential(req.params.id, key, value);
457
+ res.json({ ok: true });
458
+ } catch (err) {
459
+ res.status(400).json({ error: err.message });
460
+ }
461
+ });
462
+
463
+ app.delete('/api/gateways/:id/credentials/:key', (req, res) => {
464
+ try {
465
+ daemon.gateways.deleteCredential(req.params.id, req.params.key);
466
+ res.json({ ok: true });
467
+ } catch (err) {
468
+ res.status(400).json({ error: err.message });
469
+ }
470
+ });
471
+
472
+ app.get('/api/gateways/:id/channels', async (req, res) => {
473
+ try {
474
+ const gw = daemon.gateways.gateways.get(req.params.id);
475
+ if (!gw) return res.status(404).json({ error: 'Gateway not found' });
476
+ if (!gw.connected) return res.status(400).json({ error: 'Gateway not connected' });
477
+ if (typeof gw.listChannels !== 'function') return res.json([]);
478
+ const channels = await gw.listChannels();
479
+ res.json(channels);
480
+ } catch (err) {
481
+ res.status(400).json({ error: err.message });
482
+ }
483
+ });
484
+
485
+ }