@agent-spaces/server 0.1.2 → 0.2.1

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 (216) hide show
  1. package/dist/adapters/claude-code-runtime/index.js +4 -1
  2. package/dist/adapters/git.js +2 -21
  3. package/dist/agents/issue-agent-runner.js +35 -21
  4. package/dist/agents/issue-task-controller.js +120 -73
  5. package/dist/agents/planner-agent.js +9 -12
  6. package/dist/app.js +10 -0
  7. package/dist/hooks/agent-hooks.js +5 -8
  8. package/dist/package.json +1 -1
  9. package/dist/routes/agent.js +40 -17
  10. package/dist/routes/channel.js +4 -1
  11. package/dist/routes/command.js +108 -0
  12. package/dist/routes/folder.js +44 -7
  13. package/dist/routes/issue.js +57 -23
  14. package/dist/routes/mcp.js +50 -0
  15. package/dist/routes/skill.js +57 -0
  16. package/dist/routes/subscription.js +49 -0
  17. package/dist/routes/task.js +16 -1
  18. package/dist/routes/workflow.js +63 -0
  19. package/dist/routes/workspace.js +2 -21
  20. package/dist/services/agent.js +140 -76
  21. package/dist/services/builtin-tools.js +72 -0
  22. package/dist/services/channel.js +5 -5
  23. package/dist/services/command-process-manager.js +136 -0
  24. package/dist/services/command.js +48 -0
  25. package/dist/services/issue.js +5 -6
  26. package/dist/services/mcp.js +120 -0
  27. package/dist/services/notification-hub/bot-commands.js +48 -16
  28. package/dist/services/notification-hub/helpers.js +12 -0
  29. package/dist/services/pty.js +10 -3
  30. package/dist/services/skill.js +171 -0
  31. package/dist/services/subscription/aicode.js +45 -0
  32. package/dist/services/subscription/base.js +3 -0
  33. package/dist/services/subscription/index.js +20 -0
  34. package/dist/services/subscription/minimax.js +60 -0
  35. package/dist/services/subscription/zhipu.js +39 -0
  36. package/dist/services/workflow.js +203 -0
  37. package/dist/services/workspace.js +0 -1
  38. package/dist/storage/command-store.js +17 -0
  39. package/dist/storage/subscription-store.js +44 -0
  40. package/dist/storage/workflow-store.js +40 -0
  41. package/dist/web/404.html +1 -1
  42. package/dist/web/__next.__PAGE__.txt +4 -4
  43. package/dist/web/__next._full.txt +22 -21
  44. package/dist/web/__next._head.txt +4 -4
  45. package/dist/web/__next._index.txt +9 -8
  46. package/dist/web/__next._tree.txt +2 -2
  47. package/dist/web/_next/static/MpJepsgfsHGGcc7rwFU8Y/_buildManifest.js +11 -0
  48. package/dist/web/_next/static/chunks/0-245kun-watp.js +2 -0
  49. package/dist/web/_next/static/chunks/0-ca_fo-yp3~z.js +1 -0
  50. package/dist/web/_next/static/chunks/0-vgd3j-zh3m8.js +2 -0
  51. package/dist/web/_next/static/chunks/0.4.g.8yf4rs0.js +3 -0
  52. package/dist/web/_next/static/chunks/02m_-ngl9w8co.js +1 -0
  53. package/dist/web/_next/static/chunks/03jbh7ud0jw~g.js +1 -0
  54. package/dist/web/_next/static/chunks/07kqxmubf5dua.js +1 -0
  55. package/dist/web/_next/static/chunks/088q-5_51dsrw.js +1 -0
  56. package/dist/web/_next/static/chunks/09_ki3dc5cfwv.css +1 -0
  57. package/dist/web/_next/static/chunks/09jryrjps0vl2.js +1 -0
  58. package/dist/web/_next/static/chunks/0b2tump5duj9j.js +1 -0
  59. package/dist/web/_next/static/chunks/0dctkv_2d2r0g.js +8 -0
  60. package/dist/web/_next/static/chunks/0efgv37kxyht8.js +1 -0
  61. package/dist/web/_next/static/chunks/0fwvdy-ml8wpk.js +1 -0
  62. package/dist/web/_next/static/chunks/0g4vm6.v0o_lt.js +1 -0
  63. package/dist/web/_next/static/chunks/0g_b4t~000.o~.js +179 -0
  64. package/dist/web/_next/static/chunks/0gyede80jpy3n.js +4 -0
  65. package/dist/web/_next/static/chunks/0h256h-tu4e0r.js +2 -0
  66. package/dist/web/_next/static/chunks/0ib18ul605e~a.js +1 -0
  67. package/dist/web/_next/static/chunks/0j1g_rd9t5ot1.js +1 -0
  68. package/dist/web/_next/static/chunks/0jn~llaqcxo4q.js +1 -0
  69. package/dist/web/_next/static/chunks/0jvmviuftg5e2.css +1 -0
  70. package/dist/web/_next/static/chunks/0lb8l2~6s_owo.js +1 -0
  71. package/dist/web/_next/static/chunks/0ni5ot2603_28.js +1 -0
  72. package/dist/web/_next/static/chunks/0pep4mkvt3.rh.js +31 -0
  73. package/dist/web/_next/static/chunks/0pq2670_ezbcj.js +1 -0
  74. package/dist/web/_next/static/chunks/0q_scqkk9.t53.js +2 -0
  75. package/dist/web/_next/static/chunks/0rrdur.v1a5r7.js +1 -0
  76. package/dist/web/_next/static/chunks/0spo.tmfeas-o.js +1 -0
  77. package/dist/web/_next/static/chunks/0tta_56qj1z8-.js +1 -0
  78. package/dist/web/_next/static/chunks/0u88ij9dqqh~-.js +1 -0
  79. package/dist/web/_next/static/chunks/0zl19l5tuoppw.js +1 -0
  80. package/dist/web/_next/static/chunks/11n16hogah-5..js +1 -0
  81. package/dist/web/_next/static/chunks/1250wo~-5dgyx.js +1 -0
  82. package/dist/web/_next/static/chunks/14n8i2xz4_y-e.js +1 -0
  83. package/dist/web/_next/static/chunks/160ji-.dfvm20.js +1 -0
  84. package/dist/web/_next/static/chunks/turbopack-0lxiiw.jhevml.js +1 -0
  85. package/dist/web/_next/static/media/favicon.0~ekuj.zhggpa.ico +0 -0
  86. package/dist/web/_not-found/__next._full.txt +24 -19
  87. package/dist/web/_not-found/__next._head.txt +4 -4
  88. package/dist/web/_not-found/__next._index.txt +9 -8
  89. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  90. package/dist/web/_not-found/__next._not-found.txt +3 -3
  91. package/dist/web/_not-found/__next._tree.txt +2 -2
  92. package/dist/web/_not-found.html +1 -1
  93. package/dist/web/_not-found.txt +24 -19
  94. package/dist/web/apple-touch-icon.png +0 -0
  95. package/dist/web/favicon.ico +0 -0
  96. package/dist/web/icon-192.png +0 -0
  97. package/dist/web/icon-512.png +0 -0
  98. package/dist/web/index.html +1 -1
  99. package/dist/web/index.txt +22 -21
  100. package/dist/web/login/__next._full.txt +24 -23
  101. package/dist/web/login/__next._head.txt +4 -4
  102. package/dist/web/login/__next._index.txt +9 -8
  103. package/dist/web/login/__next._tree.txt +2 -2
  104. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  105. package/dist/web/login/__next.login.txt +3 -3
  106. package/dist/web/login.html +1 -1
  107. package/dist/web/login.txt +24 -23
  108. package/dist/web/settings/__next._full.txt +34 -0
  109. package/dist/web/settings/__next._head.txt +6 -0
  110. package/dist/web/settings/__next._index.txt +11 -0
  111. package/dist/web/settings/__next._tree.txt +8 -0
  112. package/dist/web/settings/__next.settings.__PAGE__.txt +9 -0
  113. package/dist/web/settings/__next.settings.txt +7 -0
  114. package/dist/web/settings/agents/__next._full.txt +38 -0
  115. package/dist/web/settings/agents/__next._head.txt +6 -0
  116. package/dist/web/settings/agents/__next._index.txt +11 -0
  117. package/dist/web/settings/agents/__next._tree.txt +8 -0
  118. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +9 -0
  119. package/dist/web/settings/agents/__next.settings.agents.txt +5 -0
  120. package/dist/web/settings/agents/__next.settings.txt +7 -0
  121. package/dist/web/settings/agents.html +1 -0
  122. package/dist/web/settings/agents.txt +38 -0
  123. package/dist/web/settings/mcps/__next._full.txt +38 -0
  124. package/dist/web/settings/mcps/__next._head.txt +6 -0
  125. package/dist/web/settings/mcps/__next._index.txt +11 -0
  126. package/dist/web/settings/mcps/__next._tree.txt +8 -0
  127. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +9 -0
  128. package/dist/web/settings/mcps/__next.settings.mcps.txt +5 -0
  129. package/dist/web/settings/mcps/__next.settings.txt +7 -0
  130. package/dist/web/settings/mcps.html +1 -0
  131. package/dist/web/settings/mcps.txt +38 -0
  132. package/dist/web/settings/models/__next._full.txt +38 -0
  133. package/dist/web/settings/models/__next._head.txt +6 -0
  134. package/dist/web/settings/models/__next._index.txt +11 -0
  135. package/dist/web/settings/models/__next._tree.txt +8 -0
  136. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +9 -0
  137. package/dist/web/settings/models/__next.settings.models.txt +5 -0
  138. package/dist/web/settings/models/__next.settings.txt +7 -0
  139. package/dist/web/settings/models.html +1 -0
  140. package/dist/web/settings/models.txt +38 -0
  141. package/dist/web/settings/providers/__next._full.txt +38 -0
  142. package/dist/web/settings/providers/__next._head.txt +6 -0
  143. package/dist/web/settings/providers/__next._index.txt +11 -0
  144. package/dist/web/settings/providers/__next._tree.txt +8 -0
  145. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +9 -0
  146. package/dist/web/settings/providers/__next.settings.providers.txt +5 -0
  147. package/dist/web/settings/providers/__next.settings.txt +7 -0
  148. package/dist/web/settings/providers.html +1 -0
  149. package/dist/web/settings/providers.txt +38 -0
  150. package/dist/web/settings/skills/__next._full.txt +38 -0
  151. package/dist/web/settings/skills/__next._head.txt +6 -0
  152. package/dist/web/settings/skills/__next._index.txt +11 -0
  153. package/dist/web/settings/skills/__next._tree.txt +8 -0
  154. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +9 -0
  155. package/dist/web/settings/skills/__next.settings.skills.txt +5 -0
  156. package/dist/web/settings/skills/__next.settings.txt +7 -0
  157. package/dist/web/settings/skills.html +1 -0
  158. package/dist/web/settings/skills.txt +38 -0
  159. package/dist/web/settings.html +1 -0
  160. package/dist/web/settings.txt +34 -0
  161. package/dist/web/workflows/__next._full.txt +35 -0
  162. package/dist/web/workflows/__next._head.txt +6 -0
  163. package/dist/web/workflows/__next._index.txt +11 -0
  164. package/dist/web/workflows/__next._tree.txt +9 -0
  165. package/dist/web/workflows/__next.workflows.__PAGE__.txt +10 -0
  166. package/dist/web/workflows/__next.workflows.txt +5 -0
  167. package/dist/web/workflows.html +1 -0
  168. package/dist/web/workflows.txt +35 -0
  169. package/dist/web/workspace/_/__next._full.txt +25 -20
  170. package/dist/web/workspace/_/__next._head.txt +4 -4
  171. package/dist/web/workspace/_/__next._index.txt +9 -8
  172. package/dist/web/workspace/_/__next._tree.txt +2 -2
  173. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  174. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  175. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  176. package/dist/web/workspace/_.html +1 -1
  177. package/dist/web/workspace/_.txt +25 -20
  178. package/dist/web/workspaces/__next._full.txt +24 -23
  179. package/dist/web/workspaces/__next._head.txt +4 -4
  180. package/dist/web/workspaces/__next._index.txt +9 -8
  181. package/dist/web/workspaces/__next._tree.txt +2 -2
  182. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  183. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  184. package/dist/web/workspaces.html +1 -1
  185. package/dist/web/workspaces.txt +24 -23
  186. package/dist/ws/agent-prompt.js +84 -0
  187. package/dist/ws/agent-runner.js +592 -0
  188. package/dist/ws/handler.js +9 -1200
  189. package/dist/ws/html-utils.js +30 -0
  190. package/dist/ws/message-parts.js +498 -0
  191. package/package.json +3 -2
  192. package/dist/web/_next/static/chunks/038whpa0zpnas.js +0 -1
  193. package/dist/web/_next/static/chunks/06q5go~xoz5a3.js +0 -1
  194. package/dist/web/_next/static/chunks/095.wizobwtyg.js +0 -2
  195. package/dist/web/_next/static/chunks/0bfg.w~u-83h5.js +0 -1
  196. package/dist/web/_next/static/chunks/0ce7i6~sb20rv.js +0 -113
  197. package/dist/web/_next/static/chunks/0d4~pcva1uk-a.js +0 -1
  198. package/dist/web/_next/static/chunks/0iq70n_u75nyu.js +0 -1
  199. package/dist/web/_next/static/chunks/0memz-8zsbxpu.js +0 -1
  200. package/dist/web/_next/static/chunks/0nw~w.3~twebx.js +0 -2
  201. package/dist/web/_next/static/chunks/0pyytgz~5vt0f.js +0 -31
  202. package/dist/web/_next/static/chunks/0ujjcrz~1nq.q.css +0 -1
  203. package/dist/web/_next/static/chunks/0vdnx9n41dyjl.js +0 -1
  204. package/dist/web/_next/static/chunks/0yl4mqmxtll6g.js +0 -1
  205. package/dist/web/_next/static/chunks/0zcbfka5tcle3.js +0 -1
  206. package/dist/web/_next/static/chunks/0~eo58u99i.fr.js +0 -8
  207. package/dist/web/_next/static/chunks/13_2vxyccsv84.js +0 -4
  208. package/dist/web/_next/static/chunks/13wfs~urgxu0w.js +0 -1
  209. package/dist/web/_next/static/chunks/157~3c6wqkt83.js +0 -1
  210. package/dist/web/_next/static/chunks/15jvow_z4uq-..js +0 -1
  211. package/dist/web/_next/static/chunks/15v697nf~r-cy.js +0 -2
  212. package/dist/web/_next/static/chunks/turbopack-164~7ulq9o9yc.js +0 -1
  213. package/dist/web/_next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
  214. package/dist/web/_next/static/owMnKmxATbtXK_J_k_uHh/_buildManifest.js +0 -21
  215. /package/dist/web/_next/static/{owMnKmxATbtXK_J_k_uHh → MpJepsgfsHGGcc7rwFU8Y}/_clientMiddlewareManifest.js +0 -0
  216. /package/dist/web/_next/static/{owMnKmxATbtXK_J_k_uHh → MpJepsgfsHGGcc7rwFU8Y}/_ssgManifest.js +0 -0
@@ -6,31 +6,45 @@ router.get('/usage/dashboard', (req, res) => {
6
6
  res.json(agentService.usageDashboard(Number.isFinite(days) ? days : 30));
7
7
  });
8
8
  router.get('/presets', (req, res) => {
9
- const presets = agentService.listPresets(req.params.id);
10
- if (!presets) {
11
- res.status(404).json({ error: 'workspace not found' });
12
- return;
13
- }
14
- res.json(presets);
9
+ res.json(agentService.listTemplates());
15
10
  });
16
- router.post('/presets', (req, res) => {
17
- const preset = agentService.createPreset(req.params.id, req.body);
11
+ router.get('/presets/:presetId', (req, res) => {
12
+ const preset = agentService.readAgentTemplate(req.params.presetId);
18
13
  if (!preset) {
19
- res.status(404).json({ error: 'workspace not found' });
14
+ res.status(404).json({ error: 'agent preset not found' });
20
15
  return;
21
16
  }
17
+ res.json(preset);
18
+ });
19
+ router.post('/presets', (req, res) => {
20
+ const body = req.body;
21
+ const preset = agentService.createPreset(req.params.id, body);
22
22
  res.status(201).json(preset);
23
23
  });
24
24
  router.post('/presets/test-connection', async (req, res) => {
25
- const result = await agentService.testConnection(req.params.id, req.body);
25
+ const workspaceId = req.params.id;
26
+ const data = req.body;
27
+ if (workspaceId) {
28
+ const result = await agentService.testConnection(workspaceId, data);
29
+ if (!result) {
30
+ res.status(404).json({ error: 'workspace not found' });
31
+ return;
32
+ }
33
+ res.status(result.success ? 200 : 400).json(result);
34
+ return;
35
+ }
36
+ // Global: test connection without workspace context
37
+ const { apiBase, apiKey, modelId, modelProvider } = data;
38
+ const result = await agentService.testConnection('', { apiBase, apiKey, modelId, modelProvider });
26
39
  if (!result) {
27
- res.status(404).json({ error: 'workspace not found' });
40
+ res.status(400).json({ error: 'connection test failed' });
28
41
  return;
29
42
  }
30
43
  res.status(result.success ? 200 : 400).json(result);
31
44
  });
32
45
  router.put('/presets/:presetId', (req, res) => {
33
- const preset = agentService.updatePreset(req.params.id, req.params.presetId, req.body);
46
+ const data = req.body;
47
+ const preset = agentService.updatePreset(req.params.id, req.params.presetId, data);
34
48
  if (!preset) {
35
49
  res.status(404).json({ error: 'agent preset not found' });
36
50
  return;
@@ -38,11 +52,21 @@ router.put('/presets/:presetId', (req, res) => {
38
52
  res.json(preset);
39
53
  });
40
54
  router.delete('/presets/:presetId', (req, res) => {
41
- const deleted = agentService.deletePreset(req.params.id, req.params.presetId);
42
- if (deleted === null) {
43
- res.status(404).json({ error: 'workspace not found' });
55
+ const workspaceId = req.params.id;
56
+ if (workspaceId) {
57
+ const deleted = agentService.deletePreset(workspaceId, req.params.presetId);
58
+ if (deleted === null) {
59
+ res.status(404).json({ error: 'workspace not found' });
60
+ return;
61
+ }
62
+ if (!deleted) {
63
+ res.status(404).json({ error: 'agent preset not found' });
64
+ return;
65
+ }
66
+ res.status(204).end();
44
67
  return;
45
68
  }
69
+ const deleted = agentService.deleteGlobalPreset(req.params.presetId);
46
70
  if (!deleted) {
47
71
  res.status(404).json({ error: 'agent preset not found' });
48
72
  return;
@@ -59,8 +83,7 @@ router.post('/start', (req, res) => {
59
83
  res.status(400).json({ error: 'role is required' });
60
84
  return;
61
85
  }
62
- const validRoles = ['scheduler', 'planner', 'executor', 'reviewer', 'custom', 'bot'];
63
- if (!validRoles.includes(role)) {
86
+ if (!agentService.isValidRole(role)) {
64
87
  res.status(400).json({ error: `invalid role: ${role}` });
65
88
  return;
66
89
  }
@@ -1,7 +1,8 @@
1
1
  import { Router } from 'express';
2
2
  import { listChannels, createChannel, getChannel, updateChannel, deleteChannel } from '../services/channel.js';
3
3
  import { listMessages, createMessage, updateMessage, deleteMessage, clearMessages } from '../services/message.js';
4
- import { broadcastToWorkspace, hasActiveChannelRuns, markInactiveChannelRunsStopped } from '../ws/handler.js';
4
+ import { broadcastToWorkspace } from '../ws/connection-manager.js';
5
+ import { hasActiveChannelRuns, markInactiveChannelRunsStopped, stopChannelRuns } from '../ws/agent-runner.js';
5
6
  import { getToolDetail } from '../services/tool-detail.js';
6
7
  const router = Router({ mergeParams: true });
7
8
  // GET /api/workspaces/:id/channels
@@ -38,6 +39,8 @@ router.put('/:channelId', (req, res) => {
38
39
  // DELETE /api/workspaces/:id/channels/:channelId
39
40
  router.delete('/:channelId', (req, res) => {
40
41
  const { id, channelId } = req.params;
42
+ // 先停止该频道中所有运行中的 agent
43
+ stopChannelRuns(id, channelId);
41
44
  const ok = deleteChannel(id, channelId);
42
45
  if (!ok) {
43
46
  res.status(404).json({ error: 'channel not found' });
@@ -0,0 +1,108 @@
1
+ import { Router } from 'express';
2
+ import * as commandService from '../services/command.js';
3
+ import * as processManager from '../services/command-process-manager.js';
4
+ const router = Router({ mergeParams: true });
5
+ router.get('/', (req, res) => {
6
+ const workspaceId = req.params.id;
7
+ if (!workspaceId) {
8
+ res.status(400).json({ error: 'workspaceId required' });
9
+ return;
10
+ }
11
+ try {
12
+ res.json(commandService.listCommands(workspaceId));
13
+ }
14
+ catch (error) {
15
+ res.status(500).json({ error: error.message });
16
+ }
17
+ });
18
+ router.get('/processes', (req, res) => {
19
+ const workspaceId = req.params.id;
20
+ if (!workspaceId) {
21
+ res.status(400).json({ error: 'workspaceId required' });
22
+ return;
23
+ }
24
+ res.json(processManager.getCommandProcesses(workspaceId));
25
+ });
26
+ router.post('/', (req, res) => {
27
+ const workspaceId = req.params.id;
28
+ if (!workspaceId) {
29
+ res.status(400).json({ error: 'workspaceId required' });
30
+ return;
31
+ }
32
+ try {
33
+ const cmd = commandService.createCommand(workspaceId, req.body);
34
+ res.status(201).json(cmd);
35
+ }
36
+ catch (error) {
37
+ res.status(400).json({ error: error.message });
38
+ }
39
+ });
40
+ router.put('/:commandId', (req, res) => {
41
+ const { id: workspaceId, commandId } = req.params;
42
+ if (!workspaceId || !commandId) {
43
+ res.status(400).json({ error: 'workspaceId and commandId required' });
44
+ return;
45
+ }
46
+ try {
47
+ const cmd = commandService.updateCommand(workspaceId, commandId, req.body);
48
+ res.json(cmd);
49
+ }
50
+ catch (error) {
51
+ res.status(400).json({ error: error.message });
52
+ }
53
+ });
54
+ router.delete('/:commandId', (req, res) => {
55
+ const { id: workspaceId, commandId } = req.params;
56
+ if (!workspaceId || !commandId) {
57
+ res.status(400).json({ error: 'workspaceId and commandId required' });
58
+ return;
59
+ }
60
+ try {
61
+ const process = processManager.getCommandProcess(commandId);
62
+ if (process) {
63
+ try {
64
+ processManager.stopCommand(workspaceId, commandId);
65
+ }
66
+ catch { }
67
+ }
68
+ commandService.deleteCommand(workspaceId, commandId);
69
+ res.status(204).send();
70
+ }
71
+ catch (error) {
72
+ res.status(400).json({ error: error.message });
73
+ }
74
+ });
75
+ router.post('/:commandId/run', (req, res) => {
76
+ const { id: workspaceId, commandId } = req.params;
77
+ if (!workspaceId || !commandId) {
78
+ res.status(400).json({ error: 'workspaceId and commandId required' });
79
+ return;
80
+ }
81
+ try {
82
+ const sessionId = processManager.runCommand(workspaceId, commandId);
83
+ console.log(`[command] POST run: workspace=${workspaceId} command=${commandId} -> session=${sessionId}`);
84
+ res.json({ sessionId });
85
+ }
86
+ catch (error) {
87
+ console.error(`[command] POST run error: workspace=${workspaceId} command=${commandId}`, error.message);
88
+ res.status(400).json({ error: error.message });
89
+ }
90
+ });
91
+ router.post('/:commandId/stop', (req, res) => {
92
+ const { id: workspaceId, commandId } = req.params;
93
+ if (!workspaceId || !commandId) {
94
+ res.status(400).json({ error: 'workspaceId and commandId required' });
95
+ return;
96
+ }
97
+ try {
98
+ processManager.stopCommand(workspaceId, commandId);
99
+ console.log(`[command] POST stop: workspace=${workspaceId} command=${commandId}`);
100
+ res.json({ ok: true });
101
+ }
102
+ catch (error) {
103
+ console.error(`[command] POST stop error: workspace=${workspaceId} command=${commandId}`, error.message);
104
+ res.status(400).json({ error: error.message });
105
+ }
106
+ });
107
+ export default router;
108
+ //# sourceMappingURL=command.js.map
@@ -1,41 +1,51 @@
1
1
  import { Router } from 'express';
2
- import { readdir, stat, mkdir, access } from 'node:fs/promises';
3
- import { join, resolve, sep } from 'node:path';
2
+ import { readdir, stat, mkdir, access, readFile } from 'node:fs/promises';
3
+ import { join, resolve, sep, extname } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { constants } from 'node:fs';
6
6
  const router = Router();
7
7
  router.get('/browse', async (req, res) => {
8
8
  const raw = req.query.path || '';
9
+ const includeFiles = req.query.files === '1';
10
+ const fileFilter = req.query.fileFilter || '';
9
11
  const rawPath = raw === '~' || raw === '' ? homedir() : raw.replace(/^~[/\\]/, homedir() + sep);
10
12
  const dirPath = resolve(rawPath);
11
13
  try {
12
14
  const entries = await readdir(dirPath, { withFileTypes: true });
13
15
  const directories = [];
16
+ const files = [];
14
17
  for (const entry of entries) {
15
- if (!entry.isDirectory())
16
- continue;
17
- // Skip hidden directories
18
+ // Skip hidden entries
18
19
  if (entry.name.startsWith('.'))
19
20
  continue;
20
21
  try {
21
22
  const fullPath = join(dirPath, entry.name);
22
23
  await stat(fullPath);
23
- directories.push({ name: entry.name, path: fullPath });
24
+ if (entry.isDirectory()) {
25
+ directories.push({ name: entry.name, path: fullPath });
26
+ }
27
+ else if (includeFiles && entry.isFile()) {
28
+ if (!fileFilter || entry.name.endsWith(fileFilter)) {
29
+ files.push({ name: entry.name, path: fullPath });
30
+ }
31
+ }
24
32
  }
25
33
  catch {
26
- // Skip inaccessible directories
34
+ // Skip inaccessible entries
27
35
  }
28
36
  }
29
37
  // Compute parent path
30
38
  const parentPath = resolve(dirPath, '..');
31
39
  const isRoot = parentPath === dirPath;
32
40
  directories.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
41
+ files.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
33
42
  res.json({
34
43
  path: dirPath,
35
44
  parent: isRoot ? null : parentPath,
36
45
  separator: sep,
37
46
  home: homedir(),
38
47
  directories,
48
+ files,
39
49
  });
40
50
  }
41
51
  catch (err) {
@@ -97,5 +107,32 @@ router.get('/check-permissions', async (req, res) => {
97
107
  }
98
108
  res.json(result);
99
109
  });
110
+ router.get('/read-file', async (req, res) => {
111
+ const raw = req.query.path;
112
+ if (!raw) {
113
+ res.status(400).json({ error: 'path is required' });
114
+ return;
115
+ }
116
+ const filePath = resolve(raw.replace(/^~[/\\]/, homedir() + sep));
117
+ const ext = extname(filePath).toLowerCase();
118
+ // Only allow reading text-based config files
119
+ const allowedExts = ['.json', '.yaml', '.yml', '.toml', '.txt', '.md', '.env', '.js', '.ts', '.mjs', '.cjs'];
120
+ if (!allowedExts.includes(ext)) {
121
+ res.status(400).json({ error: 'File type not allowed' });
122
+ return;
123
+ }
124
+ try {
125
+ const content = await readFile(filePath, 'utf-8');
126
+ if (ext === '.json') {
127
+ res.json(JSON.parse(content));
128
+ }
129
+ else {
130
+ res.json({ content });
131
+ }
132
+ }
133
+ catch (err) {
134
+ res.status(400).json({ error: err.message || 'Cannot read file' });
135
+ }
136
+ });
100
137
  export default router;
101
138
  //# sourceMappingURL=folder.js.map
@@ -2,11 +2,12 @@ import { Router } from 'express';
2
2
  import * as issueService from '../services/issue.js';
3
3
  import * as issueCommentService from '../services/issue-comment.js';
4
4
  import * as channelService from '../services/channel.js';
5
- import { broadcastToWorkspace } from '../ws/handler.js';
6
- import { getWorkspace } from '../storage/workspace-store.js';
5
+ import { broadcastToWorkspace } from '../ws/connection-manager.js';
6
+ import { stopChannelRuns } from '../ws/agent-runner.js';
7
7
  import * as agentService from '../services/agent.js';
8
8
  import { retryIssue } from '../services/issue-retry.js';
9
- import { hasActiveIssueAutomation, runIssueAutomation, shouldForcePlannerFromMentions } from '../agents/issue-agent-runner.js';
9
+ import { hasActiveIssueAutomation, runIssueAutomation } from '../agents/issue-agent-runner.js';
10
+ import * as workflowService from '../services/workflow.js';
10
11
  const router = Router({ mergeParams: true });
11
12
  router.get('/', (req, res) => {
12
13
  const status = req.query.status;
@@ -14,12 +15,17 @@ router.get('/', (req, res) => {
14
15
  res.json(issues);
15
16
  });
16
17
  router.post('/', (req, res) => {
17
- const { title, description, members } = req.body;
18
+ const { title, description, members, workflowId } = req.body;
18
19
  if (!title) {
19
20
  res.status(400).json({ error: 'title is required' });
20
21
  return;
21
22
  }
22
- const issue = issueService.create(req.params.id, { title, description: description || '', members });
23
+ const issue = issueService.create(req.params.id, {
24
+ title,
25
+ description: description || '',
26
+ members: mergeWorkflowMembers(members, workflowId),
27
+ workflowId,
28
+ });
23
29
  const channel = issue.channelId ? channelService.getChannel(req.params.id, issue.channelId) : null;
24
30
  if (channel)
25
31
  broadcastToWorkspace(req.params.id, 'channel.updated', channel);
@@ -40,27 +46,37 @@ router.put('/:issueId', (req, res) => {
40
46
  res.status(404).json({ error: 'issue not found' });
41
47
  return;
42
48
  }
43
- const { title, description, status, members } = req.body;
49
+ const previousStatus = issue.status;
50
+ const { title, description, status, members, workflowId } = req.body;
44
51
  if (title)
45
52
  issue.title = title;
46
53
  if (description !== undefined)
47
54
  issue.description = description;
48
- if (members) {
49
- issue.members = normalizeIssueMembers(req.params.id, members);
50
- if (issue.channelId) {
51
- const updatedChannel = channelService.updateChannel(req.params.id, issue.channelId, { members: ['user', ...issue.members] });
52
- if (updatedChannel)
53
- broadcastToWorkspace(req.params.id, 'channel.updated', updatedChannel);
54
- }
55
+ if (workflowId !== undefined)
56
+ issue.workflowId = workflowId || undefined;
57
+ if (members !== undefined) {
58
+ issue.members = normalizeIssueMembers(req.params.id, mergeWorkflowMembers(members, issue.workflowId));
59
+ }
60
+ else if (workflowId !== undefined) {
61
+ issue.members = normalizeIssueMembers(req.params.id, mergeWorkflowMembers(issue.members, issue.workflowId));
62
+ }
63
+ if ((members !== undefined || workflowId !== undefined) && issue.channelId) {
64
+ const updatedChannel = channelService.updateChannel(req.params.id, issue.channelId, { members: issue.members });
65
+ if (updatedChannel)
66
+ broadcastToWorkspace(req.params.id, 'channel.updated', updatedChannel);
55
67
  }
56
68
  if (status) {
57
- const updated = issueService.updateStatus(req.params.id, req.params.issueId, status, { title: issue.title, description: issue.description });
58
- broadcastToWorkspace(req.params.id, 'issue.updated', updated);
59
- res.json(updated);
60
- return;
69
+ issue.status = status;
61
70
  }
62
71
  const saved = issueService.save(req.params.id, issue);
63
72
  broadcastToWorkspace(req.params.id, 'issue.updated', saved);
73
+ if (status && status !== previousStatus) {
74
+ // 状态变更时停止该议题关联频道中所有运行中的 agent
75
+ if (issue.channelId) {
76
+ stopChannelRuns(req.params.id, issue.channelId);
77
+ }
78
+ broadcastToWorkspace(req.params.id, 'issue.status_changed', { issueId: req.params.issueId, from: previousStatus, to: status });
79
+ }
64
80
  res.json(saved);
65
81
  });
66
82
  router.post('/:issueId/start', (req, res) => {
@@ -108,11 +124,17 @@ router.post('/:issueId/resume', async (req, res) => {
108
124
  res.json(result.issue);
109
125
  });
110
126
  router.delete('/:issueId', (req, res) => {
111
- const ok = issueService.remove(req.params.id, req.params.issueId);
112
- if (!ok) {
127
+ const { id, issueId } = req.params;
128
+ const issue = issueService.getById(id, issueId);
129
+ if (!issue) {
113
130
  res.status(404).json({ error: 'issue not found' });
114
131
  return;
115
132
  }
133
+ // 先停止该议题关联频道中所有运行中的 agent
134
+ if (issue.channelId) {
135
+ stopChannelRuns(id, issue.channelId);
136
+ }
137
+ issueService.remove(id, issueId);
116
138
  res.status(204).send();
117
139
  });
118
140
  router.get('/:issueId/comments', (req, res) => {
@@ -164,9 +186,7 @@ router.post('/:issueId/comments', (req, res) => {
164
186
  getSession: (sessionId) => agentService.getById(workspaceId, sessionId),
165
187
  updateSessionStatus: (sessionId, status, extra) => agentService.updateStatus(workspaceId, sessionId, status, extra),
166
188
  };
167
- runIssueAutomation(workspaceId, issueId, ctx, {
168
- forcePlanner: shouldForcePlannerFromMentions(workspaceId, mentions),
169
- }).catch((err) => {
189
+ runIssueAutomation(workspaceId, issueId, ctx).catch((err) => {
170
190
  console.error(`[issue-comment] automation error for issue ${issueId}:`, err);
171
191
  });
172
192
  }
@@ -194,7 +214,7 @@ router.delete('/:issueId/comments/:commentId', (req, res) => {
194
214
  });
195
215
  export default router;
196
216
  function normalizeIssueMembers(workspaceId, members) {
197
- const agentIds = new Set((getWorkspace(workspaceId)?.agents || []).map((agent) => agent.id));
217
+ const agentIds = new Set(agentService.listPresets(workspaceId).map((agent) => agent.id));
198
218
  const normalized = [];
199
219
  const seen = new Set();
200
220
  for (const member of members) {
@@ -205,4 +225,18 @@ function normalizeIssueMembers(workspaceId, members) {
205
225
  }
206
226
  return normalized;
207
227
  }
228
+ function mergeWorkflowMembers(members, workflowId) {
229
+ const base = Array.isArray(members)
230
+ ? members.filter((member) => typeof member === 'string' && Boolean(member.trim())).map((member) => member.trim())
231
+ : [];
232
+ if (typeof workflowId !== 'string' || !workflowId.trim())
233
+ return base;
234
+ const workflow = workflowService.getWorkflow(workflowId.trim());
235
+ if (!workflow)
236
+ return base;
237
+ return Array.from(new Set([
238
+ ...base,
239
+ ...workflow.nodes.map((node) => node.data.agentConfigId).filter(Boolean),
240
+ ]));
241
+ }
208
242
  //# sourceMappingURL=issue.js.map
@@ -0,0 +1,50 @@
1
+ import { Router } from 'express';
2
+ import { listMcps, importMcps, updateMcpConfig, deleteMcp, toggleFavorite } from '../services/mcp.js';
3
+ const router = Router();
4
+ router.get('/', (_req, res) => {
5
+ res.json(listMcps());
6
+ });
7
+ router.post('/import', (req, res) => {
8
+ const { jsonText } = req.body;
9
+ if (!jsonText) {
10
+ res.status(400).json({ error: 'jsonText required' });
11
+ return;
12
+ }
13
+ try {
14
+ const mcps = importMcps(jsonText);
15
+ res.json(mcps);
16
+ }
17
+ catch (e) {
18
+ res.status(400).json({ error: 'Invalid JSON: ' + e.message });
19
+ }
20
+ });
21
+ router.put('/:name', (req, res) => {
22
+ const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
23
+ const { config } = req.body;
24
+ if (!config) {
25
+ res.status(400).json({ error: 'config required' });
26
+ return;
27
+ }
28
+ const ok = updateMcpConfig(name, config);
29
+ if (!ok) {
30
+ res.status(404).json({ error: 'MCP server not found' });
31
+ return;
32
+ }
33
+ res.json({ success: true });
34
+ });
35
+ router.post('/:name/favorite', (req, res) => {
36
+ const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
37
+ const favorited = toggleFavorite(name);
38
+ res.json({ favorited });
39
+ });
40
+ router.delete('/:name', (req, res) => {
41
+ const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
42
+ const ok = deleteMcp(name);
43
+ if (!ok) {
44
+ res.status(404).json({ error: 'MCP server not found' });
45
+ return;
46
+ }
47
+ res.json({ success: true });
48
+ });
49
+ export default router;
50
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,57 @@
1
+ import { Router } from 'express';
2
+ import { listSkills, importSkill, toggleFavorite, updateSkillContent, deleteSkill, checkSkillSync, syncSkills } from '../services/skill.js';
3
+ const router = Router();
4
+ router.get('/', (_req, res) => {
5
+ res.json(listSkills());
6
+ });
7
+ router.get('/sync-check', (_req, res) => {
8
+ res.json(checkSkillSync());
9
+ });
10
+ router.post('/sync', (req, res) => {
11
+ const { items } = req.body;
12
+ if (!Array.isArray(items) || items.length === 0) {
13
+ res.status(400).json({ error: 'items required' });
14
+ return;
15
+ }
16
+ const synced = syncSkills(items);
17
+ res.json({ synced });
18
+ });
19
+ router.post('/import', (req, res) => {
20
+ const { filename, content } = req.body;
21
+ if (!filename || !content) {
22
+ res.status(400).json({ error: 'filename and content required' });
23
+ return;
24
+ }
25
+ const skill = importSkill(filename, content);
26
+ res.json(skill);
27
+ });
28
+ router.post('/:name/favorite', (req, res) => {
29
+ const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
30
+ const favorited = toggleFavorite(name);
31
+ res.json({ favorited });
32
+ });
33
+ router.put('/:name', (req, res) => {
34
+ const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
35
+ const { content } = req.body;
36
+ if (content === undefined) {
37
+ res.status(400).json({ error: 'content required' });
38
+ return;
39
+ }
40
+ const ok = updateSkillContent(name, content);
41
+ if (!ok) {
42
+ res.status(404).json({ error: 'Skill not found' });
43
+ return;
44
+ }
45
+ res.json({ success: true });
46
+ });
47
+ router.delete('/:name', (req, res) => {
48
+ const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
49
+ const ok = deleteSkill(name);
50
+ if (!ok) {
51
+ res.status(404).json({ error: 'Skill not found' });
52
+ return;
53
+ }
54
+ res.json({ success: true });
55
+ });
56
+ export default router;
57
+ //# sourceMappingURL=skill.js.map
@@ -0,0 +1,49 @@
1
+ import { Router } from 'express';
2
+ import * as store from '../storage/subscription-store.js';
3
+ import { fetchQuota } from '../services/subscription/index.js';
4
+ const router = Router();
5
+ router.get('/', (_req, res) => {
6
+ res.json(store.listSubscriptions());
7
+ });
8
+ router.get('/:id/quota', async (req, res) => {
9
+ const config = store.getSubscription(req.params.id);
10
+ if (!config) {
11
+ res.status(404).json({ error: 'subscription not found' });
12
+ return;
13
+ }
14
+ try {
15
+ const quota = await fetchQuota(config);
16
+ res.json(quota);
17
+ }
18
+ catch (err) {
19
+ console.error(`[subscription] fetchQuota failed for ${config.provider}/${config.id}:`, err.message ?? err);
20
+ res.status(502).json({ error: err.message ?? 'Failed to fetch quota' });
21
+ }
22
+ });
23
+ router.post('/', (req, res) => {
24
+ const { provider, label, headers, cookie } = req.body;
25
+ if (!provider || (!headers && !cookie)) {
26
+ res.status(400).json({ error: 'provider and headers or cookie are required' });
27
+ return;
28
+ }
29
+ const item = store.createSubscription({ provider, label: label || provider, headers, cookie });
30
+ res.status(201).json(item);
31
+ });
32
+ router.put('/:id', (req, res) => {
33
+ const { label, headers, cookie } = req.body;
34
+ const updated = store.updateSubscription(req.params.id, { label, headers, cookie });
35
+ if (!updated) {
36
+ res.status(404).json({ error: 'subscription not found' });
37
+ return;
38
+ }
39
+ res.json(updated);
40
+ });
41
+ router.delete('/:id', (req, res) => {
42
+ if (!store.deleteSubscription(req.params.id)) {
43
+ res.status(404).json({ error: 'subscription not found' });
44
+ return;
45
+ }
46
+ res.status(204).end();
47
+ });
48
+ export default router;
49
+ //# sourceMappingURL=subscription.js.map
@@ -2,7 +2,7 @@ import { Router } from 'express';
2
2
  import * as taskService from '../services/task.js';
3
3
  import * as issueService from '../services/issue.js';
4
4
  import * as agentService from '../services/agent.js';
5
- import { broadcastToWorkspace } from '../ws/handler.js';
5
+ import { broadcastToWorkspace } from '../ws/connection-manager.js';
6
6
  import { scheduleRunnableIssueTasks } from '../agents/issue-task-controller.js';
7
7
  const router = Router({ mergeParams: true });
8
8
  router.post('/', (req, res) => {
@@ -31,6 +31,21 @@ router.get('/', (req, res) => {
31
31
  const tasks = taskService.list(req.params.id, issueId);
32
32
  res.json(tasks);
33
33
  });
34
+ router.put('/reorder', (req, res) => {
35
+ const { issueId, taskIds } = req.body;
36
+ if (!issueId || !Array.isArray(taskIds)) {
37
+ res.status(400).json({ error: 'issueId and taskIds are required' });
38
+ return;
39
+ }
40
+ const issue = issueService.getById(req.params.id, issueId);
41
+ if (!issue) {
42
+ res.status(404).json({ error: 'issue not found' });
43
+ return;
44
+ }
45
+ issueService.replaceTasks(req.params.id, issueId, taskIds);
46
+ const updated = issueService.getById(req.params.id, issueId);
47
+ res.json(updated);
48
+ });
34
49
  router.get('/:taskId', (req, res) => {
35
50
  const task = taskService.getById(req.params.id, req.params.taskId);
36
51
  if (!task) {