groove-dev 0.27.143 → 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 (186) 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/gateways/manager.js +35 -1
  6. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  7. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  8. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  9. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  10. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  11. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  12. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  13. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  14. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  22. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  23. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  24. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  25. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  26. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  29. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  30. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  33. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  34. package/node_modules/@groove-dev/gui/package.json +1 -1
  35. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  36. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  40. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  41. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  42. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  43. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  45. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  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 +409 -507
  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/packages/gui/src/{app.jsx → 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/lab/lab-assistant.jsx +316 -82
  141. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  142. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  143. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  144. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  145. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  146. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  147. package/packages/gui/src/components/network/network-health.jsx +2 -2
  148. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  149. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  150. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  151. package/packages/gui/src/lib/cron.js +64 -0
  152. package/packages/gui/src/lib/status.js +24 -24
  153. package/packages/gui/src/lib/theme-hex.js +1 -0
  154. package/packages/gui/src/stores/groove.js +34 -3144
  155. package/packages/gui/src/stores/helpers.js +10 -0
  156. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  157. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  158. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  159. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  160. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  161. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  162. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  163. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  164. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  165. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  166. package/packages/gui/src/views/agents.jsx +5 -5
  167. package/packages/gui/src/views/dashboard.jsx +12 -13
  168. package/packages/gui/src/views/marketplace.jsx +191 -3
  169. package/packages/gui/src/views/model-lab.jsx +17 -6
  170. package/packages/gui/src/views/models.jsx +409 -507
  171. package/packages/gui/src/views/network.jsx +3 -3
  172. package/packages/gui/src/views/settings.jsx +81 -94
  173. package/packages/gui/src/views/teams.jsx +40 -483
  174. package/SECURITY_SWEEP.md +0 -228
  175. package/TRAINING_DATA_v4.md +0 -6
  176. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  177. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  178. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  179. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  180. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  181. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  182. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  183. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  184. package/packages/gui/src/views/preview.jsx +0 -6
  185. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  186. 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
+ }