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,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
+ }