@agent-spaces/server 0.3.65 → 0.4.0

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 (278) hide show
  1. package/dist/adapters/agent-runtime.js +8 -0
  2. package/dist/adapters/codex-function-tool-bridge.js +152 -0
  3. package/dist/adapters/codex-runtime.js +23 -4
  4. package/dist/adapters/git.js +25 -3
  5. package/dist/adapters/hermes-runtime.js +813 -0
  6. package/dist/adapters/langchain-runtime.js +384 -21
  7. package/dist/adapters/oh-my-pi-runtime.js +858 -0
  8. package/dist/adapters/open-agent-sdk-runtime.js +202 -5
  9. package/dist/adapters/open-agent-sdk-runtime.test.js +35 -0
  10. package/dist/agents/agent-message-parts.js +52 -2
  11. package/dist/agents/issue-task-controller.js +71 -2
  12. package/dist/agents/pull-request-agent.js +115 -0
  13. package/dist/agents/scheduler-agent.js +1 -1
  14. package/dist/agents/title-generator-agent.js +120 -0
  15. package/dist/app.js +61 -4
  16. package/dist/middleware/auth.js +1 -1
  17. package/dist/package.json +2 -1
  18. package/dist/routes/agent-sse.js +70 -6
  19. package/dist/routes/channel.js +23 -2
  20. package/dist/routes/chat-run.js +191 -0
  21. package/dist/routes/chat.js +142 -0
  22. package/dist/routes/command.js +16 -0
  23. package/dist/routes/data.js +189 -0
  24. package/dist/routes/git.js +10 -1
  25. package/dist/routes/import.js +199 -0
  26. package/dist/routes/issue.js +17 -5
  27. package/dist/routes/notification.js +10 -0
  28. package/dist/routes/plugin.js +69 -0
  29. package/dist/routes/version.js +95 -0
  30. package/dist/routes/workflow-hook.js +71 -0
  31. package/dist/routes/workflow.js +282 -7
  32. package/dist/routes/workspace.js +13 -4
  33. package/dist/routes/worktree.js +93 -0
  34. package/dist/services/agent.js +123 -36
  35. package/dist/services/ai-text.js +185 -0
  36. package/dist/services/builtin-tools/index.js +1 -0
  37. package/dist/services/builtin-tools/workflow-editor-tools.js +509 -0
  38. package/dist/services/builtin-tools/workflow-exec-tools.js +320 -0
  39. package/dist/services/chat.js +134 -0
  40. package/dist/services/command-process-manager.js +16 -0
  41. package/dist/services/execution-manager.js +1346 -0
  42. package/dist/services/generated-title.js +59 -0
  43. package/dist/services/gitignore.js +22 -18
  44. package/dist/services/interaction-manager.js +114 -0
  45. package/dist/services/issue-retry.js +25 -0
  46. package/dist/services/notification-center.js +9 -0
  47. package/dist/services/output-style.js +8 -1
  48. package/dist/services/plugin.js +257 -0
  49. package/dist/services/prompt-template.js +8 -1
  50. package/dist/services/pty.js +20 -0
  51. package/dist/services/search.js +16 -6
  52. package/dist/services/skill.js +2 -0
  53. package/dist/services/version.js +69 -0
  54. package/dist/services/workflow-command-runner.js +24 -0
  55. package/dist/services/workflow-trigger-service.js +137 -0
  56. package/dist/services/workflow.js +219 -39
  57. package/dist/services/workspace.js +23 -1
  58. package/dist/services/worktree.js +207 -0
  59. package/dist/storage/agent-store.js +8 -0
  60. package/dist/storage/chat-store.js +151 -0
  61. package/dist/storage/database-store.js +6 -0
  62. package/dist/storage/json-store.js +2 -1
  63. package/dist/storage/kanban-store.js +6 -0
  64. package/dist/storage/workflow-store.js +386 -22
  65. package/dist/storage/worktree-store.js +33 -0
  66. package/dist/web/404.html +1 -1
  67. package/dist/web/__next.__PAGE__.txt +4 -4
  68. package/dist/web/__next._full.txt +21 -21
  69. package/dist/web/__next._head.txt +4 -4
  70. package/dist/web/__next._index.txt +13 -13
  71. package/dist/web/__next._tree.txt +2 -2
  72. package/dist/web/_next/static/chunks/{08x85faoz58i7.js → 0-92u-tiuy40a.js} +4 -4
  73. package/dist/web/_next/static/chunks/{0db14450oqzhg.js → 0-yizd8osmas2.js} +1 -1
  74. package/dist/web/_next/static/chunks/0.ln~3mnv8.69.js +1 -0
  75. package/dist/web/_next/static/chunks/{11cchuwoupmbf.js → 01y3e9pemva7b.js} +10 -10
  76. package/dist/web/_next/static/chunks/02l~tmzrgny.~.js +1 -0
  77. package/dist/web/_next/static/chunks/03f1bwuynjdmk.js +1 -0
  78. package/dist/web/_next/static/chunks/04cb4kc9r2pd1.js +1 -0
  79. package/dist/web/_next/static/chunks/04de6ha5_e3pp.js +1 -0
  80. package/dist/web/_next/static/chunks/09he0p79ov1rn.js +1 -0
  81. package/dist/web/_next/static/chunks/09vpocs4zuc8h.js +1 -0
  82. package/dist/web/_next/static/chunks/0_o2exgr0e_nh.js +1 -0
  83. package/dist/web/_next/static/chunks/{0xbaakj0dfc0..js → 0_y9nskc-xt2k.js} +1 -1
  84. package/dist/web/_next/static/chunks/0clnj-1fi6u8j.js +3 -0
  85. package/dist/web/_next/static/chunks/0gv40by0zzxs_.js +1 -0
  86. package/dist/web/_next/static/chunks/0iusr8y88.kjg.js +1 -0
  87. package/dist/web/_next/static/chunks/{0-a.-hl3wv99k.js → 0j-8418fxvkd1.js} +1 -1
  88. package/dist/web/_next/static/chunks/0j.wxxyoclma5.js +1 -0
  89. package/dist/web/_next/static/chunks/{0.6b64sy0g5.3.js → 0jq.16sozqik..js} +1 -1
  90. package/dist/web/_next/static/chunks/0jw8xdk_fpnp0.css +1 -0
  91. package/dist/web/_next/static/chunks/0l1lkd-wcotma.js +1 -0
  92. package/dist/web/_next/static/chunks/{0u6h-hqc_i52e.js → 0ncxf_zr4n839.js} +1 -1
  93. package/dist/web/_next/static/chunks/0ndo7um~v7vco.js +1 -0
  94. package/dist/web/_next/static/chunks/0ngk.~kan8wq_.js +1 -0
  95. package/dist/web/_next/static/chunks/0ppr271hz0xoq.js +1 -0
  96. package/dist/web/_next/static/chunks/0rt9bofnn6qvn.js +1 -0
  97. package/dist/web/_next/static/chunks/0t64.9jd00hio.js +3 -0
  98. package/dist/web/_next/static/chunks/0taui9--5n6-5.js +1 -0
  99. package/dist/web/_next/static/chunks/0ty39169lh~z3.js +1 -0
  100. package/dist/web/_next/static/chunks/0v8nof~waedlu.js +1 -0
  101. package/dist/web/_next/static/chunks/{0-pi.67w.ymsr.js → 0vcfoagnpf.aw.js} +1 -1
  102. package/dist/web/_next/static/chunks/{0vis~lx0nxgn-.js → 0wwwq.gyyz0vg.js} +3 -3
  103. package/dist/web/_next/static/chunks/0x5lak8yz7pm7.js +1 -0
  104. package/dist/web/_next/static/chunks/0xon_.4-o0_zq.js +7 -0
  105. package/dist/web/_next/static/chunks/0zjnrbt7inime.js +1 -0
  106. package/dist/web/_next/static/chunks/0zwebpitvpuv1.js +1 -0
  107. package/dist/web/_next/static/chunks/0~02o4g-o6n~m.js +1 -0
  108. package/dist/web/_next/static/chunks/{0w7.tb29958zj.js → 0~fjezp.k4qk3.js} +1 -1
  109. package/dist/web/_next/static/chunks/0~sh93re1-_rp.js +1 -0
  110. package/dist/web/_next/static/chunks/10_niiq0k6o-p.js +5 -0
  111. package/dist/web/_next/static/chunks/12tjr0_2ccrtq.js +2 -0
  112. package/dist/web/_next/static/chunks/12x5.go_x9mfk.js +1 -0
  113. package/dist/web/_next/static/chunks/{0l2w0qz1ea7ql.js → 12~5dk9i9d16t.js} +2 -2
  114. package/dist/web/_next/static/chunks/13iye4rv5p31b.js +1 -0
  115. package/dist/web/_next/static/chunks/{01~quun6sefgl.js → 13~y-b.b9rrw4.js} +1 -1
  116. package/dist/web/_next/static/chunks/151wzhb5q0lo5.js +1 -0
  117. package/dist/web/_not-found/__next._full.txt +19 -19
  118. package/dist/web/_not-found/__next._head.txt +4 -4
  119. package/dist/web/_not-found/__next._index.txt +13 -13
  120. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  121. package/dist/web/_not-found/__next._not-found.txt +3 -3
  122. package/dist/web/_not-found/__next._tree.txt +2 -2
  123. package/dist/web/_not-found.html +1 -1
  124. package/dist/web/_not-found.txt +19 -19
  125. package/dist/web/index.html +1 -1
  126. package/dist/web/index.txt +21 -21
  127. package/dist/web/login/__next._full.txt +21 -21
  128. package/dist/web/login/__next._head.txt +4 -4
  129. package/dist/web/login/__next._index.txt +13 -13
  130. package/dist/web/login/__next._tree.txt +2 -2
  131. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  132. package/dist/web/login/__next.login.txt +3 -3
  133. package/dist/web/login.html +1 -1
  134. package/dist/web/login.txt +21 -21
  135. package/dist/web/settings/__next._full.txt +23 -23
  136. package/dist/web/settings/__next._head.txt +4 -4
  137. package/dist/web/settings/__next._index.txt +13 -13
  138. package/dist/web/settings/__next._tree.txt +2 -2
  139. package/dist/web/settings/__next.settings.__PAGE__.txt +4 -4
  140. package/dist/web/settings/__next.settings.txt +5 -5
  141. package/dist/web/settings/agents/__next._full.txt +23 -23
  142. package/dist/web/settings/agents/__next._head.txt +4 -4
  143. package/dist/web/settings/agents/__next._index.txt +13 -13
  144. package/dist/web/settings/agents/__next._tree.txt +2 -2
  145. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +4 -4
  146. package/dist/web/settings/agents/__next.settings.agents.txt +3 -3
  147. package/dist/web/settings/agents/__next.settings.txt +5 -5
  148. package/dist/web/settings/agents.html +1 -1
  149. package/dist/web/settings/agents.txt +23 -23
  150. package/dist/web/settings/mcps/__next._full.txt +23 -23
  151. package/dist/web/settings/mcps/__next._head.txt +4 -4
  152. package/dist/web/settings/mcps/__next._index.txt +13 -13
  153. package/dist/web/settings/mcps/__next._tree.txt +2 -2
  154. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +4 -4
  155. package/dist/web/settings/mcps/__next.settings.mcps.txt +3 -3
  156. package/dist/web/settings/mcps/__next.settings.txt +5 -5
  157. package/dist/web/settings/mcps.html +1 -1
  158. package/dist/web/settings/mcps.txt +23 -23
  159. package/dist/web/settings/models/__next._full.txt +23 -23
  160. package/dist/web/settings/models/__next._head.txt +4 -4
  161. package/dist/web/settings/models/__next._index.txt +13 -13
  162. package/dist/web/settings/models/__next._tree.txt +2 -2
  163. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +4 -4
  164. package/dist/web/settings/models/__next.settings.models.txt +3 -3
  165. package/dist/web/settings/models/__next.settings.txt +5 -5
  166. package/dist/web/settings/models.html +1 -1
  167. package/dist/web/settings/models.txt +23 -23
  168. package/dist/web/settings/output-styles/__next._full.txt +23 -23
  169. package/dist/web/settings/output-styles/__next._head.txt +4 -4
  170. package/dist/web/settings/output-styles/__next._index.txt +13 -13
  171. package/dist/web/settings/output-styles/__next._tree.txt +2 -2
  172. package/dist/web/settings/output-styles/__next.settings.output-styles.__PAGE__.txt +4 -4
  173. package/dist/web/settings/output-styles/__next.settings.output-styles.txt +3 -3
  174. package/dist/web/settings/output-styles/__next.settings.txt +5 -5
  175. package/dist/web/settings/output-styles.html +1 -1
  176. package/dist/web/settings/output-styles.txt +23 -23
  177. package/dist/web/settings/prompts/__next._full.txt +23 -23
  178. package/dist/web/settings/prompts/__next._head.txt +4 -4
  179. package/dist/web/settings/prompts/__next._index.txt +13 -13
  180. package/dist/web/settings/prompts/__next._tree.txt +2 -2
  181. package/dist/web/settings/prompts/__next.settings.prompts.__PAGE__.txt +4 -4
  182. package/dist/web/settings/prompts/__next.settings.prompts.txt +3 -3
  183. package/dist/web/settings/prompts/__next.settings.txt +5 -5
  184. package/dist/web/settings/prompts.html +1 -1
  185. package/dist/web/settings/prompts.txt +23 -23
  186. package/dist/web/settings/providers/__next._full.txt +23 -23
  187. package/dist/web/settings/providers/__next._head.txt +4 -4
  188. package/dist/web/settings/providers/__next._index.txt +13 -13
  189. package/dist/web/settings/providers/__next._tree.txt +2 -2
  190. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +4 -4
  191. package/dist/web/settings/providers/__next.settings.providers.txt +3 -3
  192. package/dist/web/settings/providers/__next.settings.txt +5 -5
  193. package/dist/web/settings/providers.html +1 -1
  194. package/dist/web/settings/providers.txt +23 -23
  195. package/dist/web/settings/skills/__next._full.txt +23 -23
  196. package/dist/web/settings/skills/__next._head.txt +4 -4
  197. package/dist/web/settings/skills/__next._index.txt +13 -13
  198. package/dist/web/settings/skills/__next._tree.txt +2 -2
  199. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +4 -4
  200. package/dist/web/settings/skills/__next.settings.skills.txt +3 -3
  201. package/dist/web/settings/skills/__next.settings.txt +5 -5
  202. package/dist/web/settings/skills.html +1 -1
  203. package/dist/web/settings/skills.txt +23 -23
  204. package/dist/web/settings/tools/__next._full.txt +23 -23
  205. package/dist/web/settings/tools/__next._head.txt +4 -4
  206. package/dist/web/settings/tools/__next._index.txt +13 -13
  207. package/dist/web/settings/tools/__next._tree.txt +2 -2
  208. package/dist/web/settings/tools/__next.settings.tools.__PAGE__.txt +4 -4
  209. package/dist/web/settings/tools/__next.settings.tools.txt +3 -3
  210. package/dist/web/settings/tools/__next.settings.txt +5 -5
  211. package/dist/web/settings/tools.html +1 -1
  212. package/dist/web/settings/tools.txt +23 -23
  213. package/dist/web/settings.html +1 -1
  214. package/dist/web/settings.txt +23 -23
  215. package/dist/web/workflows/__next._full.txt +22 -22
  216. package/dist/web/workflows/__next._head.txt +4 -4
  217. package/dist/web/workflows/__next._index.txt +13 -13
  218. package/dist/web/workflows/__next._tree.txt +2 -2
  219. package/dist/web/workflows/__next.workflows.__PAGE__.txt +4 -4
  220. package/dist/web/workflows/__next.workflows.txt +3 -3
  221. package/dist/web/workflows.html +1 -1
  222. package/dist/web/workflows.txt +22 -22
  223. package/dist/web/workspace/_/__next._full.txt +20 -20
  224. package/dist/web/workspace/_/__next._head.txt +4 -4
  225. package/dist/web/workspace/_/__next._index.txt +13 -13
  226. package/dist/web/workspace/_/__next._tree.txt +2 -2
  227. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  228. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  229. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  230. package/dist/web/workspace/_.html +1 -1
  231. package/dist/web/workspace/_.txt +20 -20
  232. package/dist/web/workspaces/__next._full.txt +21 -21
  233. package/dist/web/workspaces/__next._head.txt +4 -4
  234. package/dist/web/workspaces/__next._index.txt +13 -13
  235. package/dist/web/workspaces/__next._tree.txt +2 -2
  236. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  237. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  238. package/dist/web/workspaces.html +1 -1
  239. package/dist/web/workspaces.txt +21 -21
  240. package/dist/ws/agent-prompt.js +36 -3
  241. package/dist/ws/agent-runner.js +30 -18
  242. package/dist/ws/chat-handler.js +89 -0
  243. package/dist/ws/connection-manager.js +49 -2
  244. package/dist/ws/execution-channels.js +88 -0
  245. package/dist/ws/handler.js +32 -5
  246. package/dist/ws/message-parts.js +130 -12
  247. package/dist/ws/terminal-handler.js +26 -0
  248. package/package.json +13 -1
  249. package/public/avatars/user.jpg +0 -0
  250. package/dist/web/_next/static/chunks/0-v_p6j5u3_9l.js +0 -1
  251. package/dist/web/_next/static/chunks/0.-x29t-8gk0r.js +0 -1
  252. package/dist/web/_next/static/chunks/0.l5e8~r7_0~p.js +0 -1
  253. package/dist/web/_next/static/chunks/0.n~yfwf4~6w-.js +0 -1
  254. package/dist/web/_next/static/chunks/00-syqzi-6zxr.js +0 -1
  255. package/dist/web/_next/static/chunks/02j4sr9psucf-.js +0 -1
  256. package/dist/web/_next/static/chunks/06si8l0doqe8p.js +0 -1
  257. package/dist/web/_next/static/chunks/08kfx8on9oty~.js +0 -1
  258. package/dist/web/_next/static/chunks/08ugbvzr20drp.js +0 -2
  259. package/dist/web/_next/static/chunks/0_cqojbge48kq.js +0 -5
  260. package/dist/web/_next/static/chunks/0d6~~lv0-6kex.js +0 -3
  261. package/dist/web/_next/static/chunks/0gdwop9cnpmtu.js +0 -1
  262. package/dist/web/_next/static/chunks/0ghx3j_.-gdyd.js +0 -1
  263. package/dist/web/_next/static/chunks/0kbvoxla7f751.js +0 -1
  264. package/dist/web/_next/static/chunks/0paaat8y96ayc.js +0 -7
  265. package/dist/web/_next/static/chunks/0pq318f8xgafg.js +0 -1
  266. package/dist/web/_next/static/chunks/0t6s8of0qr6nc.js +0 -1
  267. package/dist/web/_next/static/chunks/0xt-8wk21gs7g.js +0 -1
  268. package/dist/web/_next/static/chunks/0yd.m1afqg.2t.js +0 -3
  269. package/dist/web/_next/static/chunks/0yhbr8hl09b-5.js +0 -1
  270. package/dist/web/_next/static/chunks/0z~vqzbm_q371.js +0 -1
  271. package/dist/web/_next/static/chunks/0~~_ltu6dx_ch.js +0 -1
  272. package/dist/web/_next/static/chunks/12oxcgyhpaxw~.js +0 -1
  273. package/dist/web/_next/static/chunks/1452eibz62gs6.css +0 -1
  274. package/dist/web/_next/static/chunks/16zlreiems5cn.js +0 -1
  275. package/dist/web/_next/static/chunks/18awqjk9-o5m0.js +0 -1
  276. /package/dist/web/_next/static/{x4-CsKe6qUQyFDWVtfvUg → rGXyAK8rgod1NjF3bLZAY}/_buildManifest.js +0 -0
  277. /package/dist/web/_next/static/{x4-CsKe6qUQyFDWVtfvUg → rGXyAK8rgod1NjF3bLZAY}/_clientMiddlewareManifest.js +0 -0
  278. /package/dist/web/_next/static/{x4-CsKe6qUQyFDWVtfvUg → rGXyAK8rgod1NjF3bLZAY}/_ssgManifest.js +0 -0
@@ -0,0 +1,142 @@
1
+ import { Router } from 'express';
2
+ import * as svc from '../services/chat.js';
3
+ import * as fileService from '../services/file.js';
4
+ const router = Router();
5
+ // GET /api/chat/agents — list all chat agents
6
+ router.get('/agents', (_req, res) => {
7
+ try {
8
+ res.json(svc.listAgents());
9
+ }
10
+ catch (error) {
11
+ res.status(500).json({ error: error.message });
12
+ }
13
+ });
14
+ // POST /api/chat/agents — create agent (requires name, provider, model, apiKey)
15
+ router.post('/agents', (req, res) => {
16
+ const body = req.body;
17
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
18
+ const provider = stringValue(body.provider) || stringValue(body.modelProvider);
19
+ const model = stringValue(body.model) || stringValue(body.modelId);
20
+ if (!name || !provider || !model) {
21
+ res.status(400).json({ error: 'name, provider, and model are required' });
22
+ return;
23
+ }
24
+ try {
25
+ const agent = svc.createAgent(body);
26
+ res.status(201).json(agent);
27
+ }
28
+ catch (error) {
29
+ res.status(500).json({ error: error.message });
30
+ }
31
+ });
32
+ // PUT /api/chat/agents/:id — update agent
33
+ router.put('/agents/:id', (req, res) => {
34
+ const { id } = req.params;
35
+ if (!id) {
36
+ res.status(400).json({ error: 'id is required' });
37
+ return;
38
+ }
39
+ try {
40
+ const patch = normalizeChatAgentPatch(req.body);
41
+ const agent = svc.updateAgent(id, patch);
42
+ if (!agent) {
43
+ res.status(404).json({ error: 'Agent not found' });
44
+ return;
45
+ }
46
+ res.json(agent);
47
+ }
48
+ catch (error) {
49
+ res.status(500).json({ error: error.message });
50
+ }
51
+ });
52
+ function normalizeChatAgentPatch(body) {
53
+ if (!body || typeof body !== 'object' || Array.isArray(body))
54
+ return {};
55
+ const patch = { ...body };
56
+ if (!patch.baseURL && patch.apiBase) {
57
+ patch.baseURL = patch.apiBase;
58
+ }
59
+ delete patch.apiBase;
60
+ return patch;
61
+ }
62
+ // DELETE /api/chat/agents/:id — delete agent + its messages
63
+ router.delete('/agents/:id', (req, res) => {
64
+ const { id } = req.params;
65
+ if (!id) {
66
+ res.status(400).json({ error: 'id is required' });
67
+ return;
68
+ }
69
+ try {
70
+ const deleted = svc.deleteAgent(id);
71
+ if (!deleted) {
72
+ res.status(404).json({ error: 'Agent not found' });
73
+ return;
74
+ }
75
+ res.status(204).send();
76
+ }
77
+ catch (error) {
78
+ res.status(500).json({ error: error.message });
79
+ }
80
+ });
81
+ // GET /api/chat/agents/:id/messages — list messages (?limit=50&before=msgId)
82
+ router.get('/agents/:id/messages', (req, res) => {
83
+ const { id } = req.params;
84
+ if (!id) {
85
+ res.status(400).json({ error: 'id is required' });
86
+ return;
87
+ }
88
+ try {
89
+ const agent = svc.findAgent(id);
90
+ if (!agent) {
91
+ res.status(404).json({ error: 'Agent not found' });
92
+ return;
93
+ }
94
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : undefined;
95
+ const before = req.query.before;
96
+ res.json(svc.listMessages(id, limit, before));
97
+ }
98
+ catch (error) {
99
+ res.status(500).json({ error: error.message });
100
+ }
101
+ });
102
+ // DELETE /api/chat/agents/:id/messages — clear messages
103
+ router.delete('/agents/:id/messages', (req, res) => {
104
+ const { id } = req.params;
105
+ if (!id) {
106
+ res.status(400).json({ error: 'id is required' });
107
+ return;
108
+ }
109
+ try {
110
+ svc.clearMessages(id);
111
+ res.status(204).send();
112
+ }
113
+ catch (error) {
114
+ res.status(500).json({ error: error.message });
115
+ }
116
+ });
117
+ // GET /api/chat/agents/:id/workspace/tree - read chat agent working directory
118
+ router.get('/agents/:id/workspace/tree', async (req, res) => {
119
+ const { id } = req.params;
120
+ if (!id) {
121
+ res.status(400).json({ error: 'id is required' });
122
+ return;
123
+ }
124
+ try {
125
+ const workspace = svc.getAgentWorkspace(id);
126
+ if (!workspace) {
127
+ res.status(404).json({ error: 'Agent workspace not found' });
128
+ return;
129
+ }
130
+ const relPath = typeof req.query.path === 'string' ? req.query.path : '';
131
+ const depth = req.query.depth ? Number(req.query.depth) : Infinity;
132
+ res.json(await fileService.readTree(workspace, relPath, depth));
133
+ }
134
+ catch (error) {
135
+ res.status(500).json({ error: error.message });
136
+ }
137
+ });
138
+ function stringValue(value) {
139
+ return typeof value === 'string' ? value.trim() : '';
140
+ }
141
+ export default router;
142
+ //# sourceMappingURL=chat.js.map
@@ -104,5 +104,21 @@ router.post('/:commandId/stop', (req, res) => {
104
104
  res.status(400).json({ error: error.message });
105
105
  }
106
106
  });
107
+ router.post('/:commandId/restart', (req, res) => {
108
+ const { id: workspaceId, commandId } = req.params;
109
+ if (!workspaceId || !commandId) {
110
+ res.status(400).json({ error: 'workspaceId and commandId required' });
111
+ return;
112
+ }
113
+ try {
114
+ const sessionId = processManager.restartCommand(workspaceId, commandId);
115
+ console.log(`[command] POST restart: workspace=${workspaceId} command=${commandId} -> session=${sessionId}`);
116
+ res.json({ sessionId });
117
+ }
118
+ catch (error) {
119
+ console.error(`[command] POST restart error: workspace=${workspaceId} command=${commandId}`, error.message);
120
+ res.status(400).json({ error: error.message });
121
+ }
122
+ });
107
123
  export default router;
108
124
  //# sourceMappingURL=command.js.map
@@ -0,0 +1,189 @@
1
+ import { Router } from 'express';
2
+ import { existsSync, statSync, rmSync, cpSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { randomUUID } from 'node:crypto';
6
+ import archiver from 'archiver';
7
+ import AdmZip from 'adm-zip';
8
+ import multer from 'multer';
9
+ import { getDataDir } from '../storage/json-store.js';
10
+ import * as agentStore from '../storage/agent-store.js';
11
+ import * as databaseStore from '../storage/database-store.js';
12
+ import * as kanbanStore from '../storage/kanban-store.js';
13
+ const router = Router();
14
+ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 500 * 1024 * 1024 } });
15
+ const CATEGORIES = {
16
+ 'auth': { path: 'auth.json', label: 'Authentication', group: 'config' },
17
+ 'user-settings': { path: 'user-settings.json', label: 'User Settings', group: 'config' },
18
+ 'robot-accounts': { path: 'robot-accounts.json', label: 'Robot Accounts', group: 'config' },
19
+ 'speech-recognition': { path: 'speech-recognition.json', label: 'Speech Recognition', group: 'config' },
20
+ 'llm': { path: 'llm', label: 'LLM Models & Providers', group: 'ai' },
21
+ 'agents': { path: 'agents', label: 'Agent Usage', group: 'content' },
22
+ 'database': { path: 'database', label: 'Document Database', group: 'content' },
23
+ 'kanban': { path: 'kanban', label: 'Kanban Boards', group: 'content' },
24
+ 'output-styles': { path: 'output-styles', label: 'Output Styles', group: 'customization' },
25
+ 'prompt-templates': { path: 'prompt-templates', label: 'Prompt Templates', group: 'customization' },
26
+ 'subscriptions': { path: 'subscriptions', label: 'Subscriptions', group: 'billing' },
27
+ 'skills': { path: 'skills', label: 'Skills', group: 'customization' },
28
+ 'mcps': { path: 'mcps', label: 'MCP Servers', group: 'customization' },
29
+ 'agent-templates': { path: 'agent-templates', label: 'Agent Templates', group: 'customization' },
30
+ 'workflows': { path: 'workflows', label: 'Workflows', group: 'content' },
31
+ };
32
+ // GET /api/data/export — stream zip backup
33
+ router.get('/export', (_req, res) => {
34
+ const dataDir = getDataDir();
35
+ const timestamp = new Date().toISOString().slice(0, 10);
36
+ res.setHeader('Content-Type', 'application/zip');
37
+ res.setHeader('Content-Disposition', `attachment; filename="agent-spaces-backup-${timestamp}.zip"`);
38
+ const archive = archiver('zip', { zlib: { level: 6 } });
39
+ archive.on('error', (err) => {
40
+ console.error('[data-export] archive error:', err);
41
+ if (!res.headersSent) {
42
+ res.status(500).json({ error: 'Failed to create archive' });
43
+ }
44
+ else {
45
+ res.end();
46
+ }
47
+ });
48
+ archive.pipe(res);
49
+ for (const { path: relPath } of Object.values(CATEGORIES)) {
50
+ const fullPath = join(dataDir, relPath);
51
+ if (!existsSync(fullPath))
52
+ continue;
53
+ const stat = statSync(fullPath);
54
+ if (stat.isDirectory()) {
55
+ archive.directory(fullPath, relPath);
56
+ }
57
+ else {
58
+ archive.file(fullPath, { name: relPath });
59
+ }
60
+ }
61
+ archive.finalize();
62
+ });
63
+ // POST /api/data/import/preview — upload zip, extract, return categories
64
+ router.post('/import/preview', upload.single('file'), (req, res) => {
65
+ if (!req.file?.buffer) {
66
+ res.status(400).json({ error: 'No file uploaded' });
67
+ return;
68
+ }
69
+ try {
70
+ const zip = new AdmZip(req.file.buffer);
71
+ const sessionId = randomUUID();
72
+ const tempDir = join(tmpdir(), `agent-spaces-import-${sessionId}`);
73
+ zip.extractAllTo(tempDir, true);
74
+ const found = [];
75
+ for (const [key, { path: relPath, label, group }] of Object.entries(CATEGORIES)) {
76
+ const fullPath = join(tempDir, relPath);
77
+ if (!existsSync(fullPath))
78
+ continue;
79
+ const stat = statSync(fullPath);
80
+ const isDir = stat.isDirectory();
81
+ found.push({
82
+ key,
83
+ label,
84
+ group,
85
+ size: isDir ? getDirSize(fullPath) : stat.size,
86
+ type: isDir ? 'directory' : 'file',
87
+ details: isDir ? `${countFiles(fullPath)} files` : formatSize(stat.size),
88
+ });
89
+ }
90
+ activeImportSessions.set(sessionId, { tempDir, expiresAt: Date.now() + 30 * 60 * 1000 });
91
+ res.json({ sessionId, categories: found });
92
+ }
93
+ catch (e) {
94
+ res.status(500).json({ error: e.message });
95
+ }
96
+ });
97
+ // POST /api/data/import/execute — restore selected categories
98
+ router.post('/import/execute', (req, res) => {
99
+ const { sessionId, categories: selectedCategories } = req.body;
100
+ if (!sessionId || !selectedCategories?.length) {
101
+ res.status(400).json({ error: 'sessionId and categories are required' });
102
+ return;
103
+ }
104
+ const session = activeImportSessions.get(sessionId);
105
+ if (!session || Date.now() > session.expiresAt) {
106
+ res.status(410).json({ error: 'Import session expired. Please upload again.' });
107
+ return;
108
+ }
109
+ const dataDir = getDataDir();
110
+ const results = {};
111
+ for (const categoryKey of selectedCategories) {
112
+ const category = CATEGORIES[categoryKey];
113
+ if (!category) {
114
+ results[categoryKey] = 'skipped';
115
+ continue;
116
+ }
117
+ try {
118
+ const srcPath = join(session.tempDir, category.path);
119
+ const destPath = join(dataDir, category.path);
120
+ if (!existsSync(srcPath)) {
121
+ results[categoryKey] = 'skipped';
122
+ continue;
123
+ }
124
+ // Close SQLite connections before overwriting
125
+ if (categoryKey === 'agents')
126
+ agentStore.closeDb();
127
+ else if (categoryKey === 'database')
128
+ databaseStore.closeDb();
129
+ else if (categoryKey === 'kanban')
130
+ kanbanStore.closeDb();
131
+ if (statSync(srcPath).isDirectory()) {
132
+ if (existsSync(destPath))
133
+ rmSync(destPath, { recursive: true, force: true });
134
+ cpSync(srcPath, destPath, { recursive: true });
135
+ }
136
+ else {
137
+ cpSync(srcPath, destPath);
138
+ }
139
+ results[categoryKey] = 'ok';
140
+ }
141
+ catch (e) {
142
+ console.error(`[data-import] error importing ${categoryKey}:`, e);
143
+ results[categoryKey] = 'error';
144
+ }
145
+ }
146
+ // Cleanup
147
+ rmSync(session.tempDir, { recursive: true, force: true });
148
+ activeImportSessions.delete(sessionId);
149
+ res.json({ results });
150
+ });
151
+ // --- In-memory import sessions with auto-cleanup ---
152
+ const activeImportSessions = new Map();
153
+ setInterval(() => {
154
+ const now = Date.now();
155
+ for (const [id, session] of activeImportSessions) {
156
+ if (now > session.expiresAt) {
157
+ rmSync(session.tempDir, { recursive: true, force: true });
158
+ activeImportSessions.delete(id);
159
+ }
160
+ }
161
+ }, 10 * 60 * 1000).unref();
162
+ // --- Helpers ---
163
+ function listFilesRecursive(dir) {
164
+ const results = [];
165
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
166
+ if (entry.isDirectory()) {
167
+ results.push(...listFilesRecursive(join(dir, entry.name)));
168
+ }
169
+ else {
170
+ results.push(join(dir, entry.name));
171
+ }
172
+ }
173
+ return results;
174
+ }
175
+ function getDirSize(dir) {
176
+ return listFilesRecursive(dir).reduce((sum, f) => sum + statSync(f).size, 0);
177
+ }
178
+ function countFiles(dir) {
179
+ return listFilesRecursive(dir).length;
180
+ }
181
+ function formatSize(bytes) {
182
+ if (bytes < 1024)
183
+ return `${bytes} B`;
184
+ if (bytes < 1024 * 1024)
185
+ return `${(bytes / 1024).toFixed(1)} KB`;
186
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
187
+ }
188
+ export default router;
189
+ //# sourceMappingURL=data.js.map
@@ -1,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { gitStatus, gitDiff, gitLog, gitCommit, gitDiscard, gitDiscardAll, gitBranches, gitCheckout, gitInit, gitGenerateCommitMsg, gitPush, gitPull, gitFetch, gitGetRemotes, gitAddRemote, gitCheckoutDetached, gitCherryPick, gitCreateBranch, gitDeleteBranch, gitCreateTag, gitCommitDiff, gitGetRemoteUrl, gitMergeBase, gitGetConfig, gitSetConfig, gitStage, gitUnstage, gitResolveFile } from '../adapters/git.js';
2
+ import { gitStatus, gitDiff, gitLog, gitCommit, gitDiscard, gitDiscardAll, gitBranches, gitCheckout, gitInit, gitGenerateCommitMsg, gitPush, gitPull, gitFetch, gitGetRemotes, gitAddRemote, gitCheckoutDetached, gitCherryPick, gitCreateBranch, gitDeleteBranch, gitCreateTag, gitCommitDiff, gitGetRemoteUrl, gitMergeBase, gitGetConfig, gitSetConfig, gitStage, gitUnstage, gitResolveFile, gitReset } from '../adapters/git.js';
3
3
  import { logGitOperation, getGitOperations } from '../services/git-operation-log.js';
4
4
  const router = Router({ mergeParams: true });
5
5
  function withLog(operation, inputFn, handler) {
@@ -201,5 +201,14 @@ router.post('/config', withLog('config.set', (req) => ({ name: req.body?.name, e
201
201
  router.get('/operations', (req, res) => {
202
202
  res.json(getGitOperations(req.params.id));
203
203
  });
204
+ router.post('/reset', withLog('reset', (req) => ({ commitHash: req.body?.commitHash, mode: req.body?.mode }), async (req, res) => {
205
+ const { commitHash, mode } = req.body;
206
+ if (!commitHash) {
207
+ res.status(400).json({ error: 'commitHash is required' });
208
+ return;
209
+ }
210
+ await gitReset(req.params.id, commitHash, mode || 'mixed');
211
+ res.json({ ok: true });
212
+ }));
204
213
  export default router;
205
214
  //# sourceMappingURL=git.js.map
@@ -0,0 +1,199 @@
1
+ import { Router } from 'express';
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ import * as llmStore from '../storage/llm-store.js';
6
+ import { importSkillsBatch } from '../services/skill.js';
7
+ import { importMcps } from '../services/mcp.js';
8
+ const router = Router();
9
+ const CC_SWITCH_DIR = join(homedir(), '.cc-switch');
10
+ const CONFIG_FILE = join(CC_SWITCH_DIR, 'config.json.migrated');
11
+ const SKILLS_DIR = join(CC_SWITCH_DIR, 'skills');
12
+ const MODEL_ENV_KEYS = [
13
+ 'ANTHROPIC_MODEL',
14
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
15
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
16
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
17
+ ];
18
+ function extractModels(env) {
19
+ const seen = new Set();
20
+ for (const key of MODEL_ENV_KEYS) {
21
+ const val = env[key];
22
+ if (val && !seen.has(val))
23
+ seen.add(val);
24
+ }
25
+ return [...seen];
26
+ }
27
+ function parseProviders(config) {
28
+ const results = [];
29
+ const sections = ['claude', 'codex', 'gemini'];
30
+ const envMappings = {
31
+ claude: { base: 'ANTHROPIC_BASE_URL', key: 'ANTHROPIC_AUTH_TOKEN' },
32
+ codex: { base: 'CODEX_BASE_URL', key: 'OPENAI_API_KEY' },
33
+ gemini: { base: 'GOOGLE_GEMINI_BASE_URL', key: 'GEMINI_API_KEY' },
34
+ };
35
+ for (const section of sections) {
36
+ const providers = config[section]?.providers;
37
+ if (!providers)
38
+ continue;
39
+ const { base: baseKey, key: apiKeyKey } = envMappings[section];
40
+ for (const [id, p] of Object.entries(providers)) {
41
+ const env = p.settingsConfig?.env || {};
42
+ let apiBase = env[baseKey] || '';
43
+ let apiKey = env[apiKeyKey] || '';
44
+ if (section === 'codex') {
45
+ apiKey = apiKey || p.settingsConfig?.auth?.OPENAI_API_KEY || '';
46
+ if (!apiBase) {
47
+ const configStr = p.settingsConfig?.config || '';
48
+ const match = configStr.match(/base_url\s*=\s*"([^"]+)"/);
49
+ if (match)
50
+ apiBase = match[1];
51
+ }
52
+ }
53
+ if (section === 'claude') {
54
+ apiKey = apiKey || env.ANTHROPIC_API_KEY || '';
55
+ }
56
+ if (!apiKey && !apiBase)
57
+ continue;
58
+ results.push({
59
+ sourceId: id,
60
+ name: p.name || id,
61
+ apiBase,
62
+ apiKey,
63
+ source: section,
64
+ websiteUrl: p.websiteUrl,
65
+ category: p.category,
66
+ models: extractModels(env),
67
+ });
68
+ }
69
+ }
70
+ return results;
71
+ }
72
+ function parseSkills() {
73
+ if (!existsSync(SKILLS_DIR))
74
+ return [];
75
+ const results = [];
76
+ const folders = readdirSync(SKILLS_DIR).filter(f => {
77
+ const p = join(SKILLS_DIR, f);
78
+ return statSync(p).isDirectory() && !f.startsWith('_');
79
+ });
80
+ for (const folder of folders) {
81
+ const skillFile = join(SKILLS_DIR, folder, 'SKILL.md');
82
+ if (!existsSync(skillFile))
83
+ continue;
84
+ const content = readFileSync(skillFile, 'utf-8');
85
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
86
+ let description = '';
87
+ if (fmMatch) {
88
+ const descMatch = fmMatch[1].match(/description:\s*["']?(.+?)["']?\s*$/m);
89
+ if (descMatch)
90
+ description = descMatch[1];
91
+ }
92
+ results.push({ name: folder, path: skillFile, description });
93
+ }
94
+ return results;
95
+ }
96
+ function parseMcps(config) {
97
+ const servers = config.mcp?.servers;
98
+ if (!servers)
99
+ return [];
100
+ const results = [];
101
+ for (const [, s] of Object.entries(servers)) {
102
+ if (!s.server?.command)
103
+ continue;
104
+ results.push({
105
+ name: s.name || s.id,
106
+ command: s.server.command,
107
+ args: s.server.args || [],
108
+ env: s.server.env || {},
109
+ });
110
+ }
111
+ return results;
112
+ }
113
+ // GET /api/import/cc-switch/preview
114
+ router.get('/cc-switch/preview', (_req, res) => {
115
+ if (!existsSync(CONFIG_FILE)) {
116
+ res.json({ providers: [], skills: [], mcps: [], error: 'cc-switch config not found' });
117
+ return;
118
+ }
119
+ try {
120
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
121
+ const config = JSON.parse(raw);
122
+ const providers = parseProviders(config);
123
+ const skills = parseSkills();
124
+ const mcps = parseMcps(config);
125
+ res.json({ providers, skills, mcps });
126
+ }
127
+ catch (e) {
128
+ res.status(500).json({ error: e.message });
129
+ }
130
+ });
131
+ // POST /api/import/cc-switch/execute
132
+ router.post('/cc-switch/execute', (req, res) => {
133
+ const { providers: selectedProviders, skills: selectedSkills, mcps: selectedMcps } = req.body;
134
+ const results = { providers: [], models: [], skills: [], mcps: [] };
135
+ // Import providers + models
136
+ if (selectedProviders?.length) {
137
+ const existingProviders = llmStore.listProviders();
138
+ const existingModels = llmStore.listModels();
139
+ for (const p of selectedProviders) {
140
+ if (!p.name)
141
+ continue;
142
+ // skip if provider with same name already exists
143
+ let provider = existingProviders.find(ep => ep.name === p.name);
144
+ if (!provider) {
145
+ provider = llmStore.createProvider({
146
+ name: p.name,
147
+ apiBase: p.apiBase || '',
148
+ apiKey: p.apiKey || '',
149
+ });
150
+ results.providers.push(p.name);
151
+ }
152
+ else {
153
+ results.providers.push(`${p.name} (skipped: exists)`);
154
+ }
155
+ // import associated models (deduplicated)
156
+ for (const modelId of p.models) {
157
+ if (existingModels.some(m => m.modelId === modelId && m.provider === provider.id))
158
+ continue;
159
+ llmStore.createModel({
160
+ modelId,
161
+ name: modelId,
162
+ provider: provider.id,
163
+ thinkingEnabled: true,
164
+ thinkingEffort: 'medium',
165
+ vision: false,
166
+ reasoning: false,
167
+ embedding: false,
168
+ });
169
+ results.models.push(modelId);
170
+ }
171
+ }
172
+ }
173
+ // Import skills
174
+ if (selectedSkills?.length) {
175
+ const items = [];
176
+ for (const s of selectedSkills) {
177
+ const skillFile = join(SKILLS_DIR, s.name, 'SKILL.md');
178
+ if (!existsSync(skillFile))
179
+ continue;
180
+ items.push({ name: s.name, content: readFileSync(skillFile, 'utf-8') });
181
+ }
182
+ if (items.length) {
183
+ const imported = importSkillsBatch(items);
184
+ results.skills = imported.map(s => s.name);
185
+ }
186
+ }
187
+ // Import MCP servers
188
+ if (selectedMcps?.length) {
189
+ const mcpServers = {};
190
+ for (const m of selectedMcps) {
191
+ mcpServers[m.name] = { command: m.command, args: m.args, env: m.env };
192
+ }
193
+ const imported = importMcps(JSON.stringify(mcpServers));
194
+ results.mcps = imported.map(m => m.name);
195
+ }
196
+ res.json(results);
197
+ });
198
+ export default router;
199
+ //# sourceMappingURL=import.js.map
@@ -10,6 +10,7 @@ import { retryIssue } from '../services/issue-retry.js';
10
10
  import { hasActiveIssueAutomation, runIssueAutomation } from '../agents/issue-agent-runner.js';
11
11
  import { continueIssueTaskScheduling, stopIssueAutomation } from '../agents/issue-task-controller.js';
12
12
  import * as workflowService from '../services/workflow.js';
13
+ import { scheduleIssueTitleGeneration } from '../services/generated-title.js';
13
14
  const router = Router({ mergeParams: true });
14
15
  router.get('/', (req, res) => {
15
16
  const status = req.query.status;
@@ -18,13 +19,15 @@ router.get('/', (req, res) => {
18
19
  });
19
20
  router.post('/', (req, res) => {
20
21
  const { title, description, members, workflowId } = req.body;
21
- if (!title) {
22
- res.status(400).json({ error: 'title is required' });
22
+ const trimmedTitle = typeof title === 'string' ? title.trim() : '';
23
+ const trimmedDescription = typeof description === 'string' ? description.trim() : '';
24
+ if (!trimmedTitle && !trimmedDescription) {
25
+ res.status(400).json({ error: 'title or description is required' });
23
26
  return;
24
27
  }
25
28
  const issue = issueService.create(req.params.id, {
26
- title,
27
- description: description || '',
29
+ title: trimmedTitle,
30
+ description: trimmedDescription,
28
31
  members: mergeWorkflowMembers(members, workflowId),
29
32
  workflowId,
30
33
  });
@@ -33,6 +36,15 @@ router.post('/', (req, res) => {
33
36
  broadcastToWorkspace(req.params.id, 'channel.updated', channel);
34
37
  broadcastToWorkspace(req.params.id, 'issue.created', issue);
35
38
  res.status(201).json(issue);
39
+ if (!trimmedTitle) {
40
+ scheduleIssueTitleGeneration({
41
+ workspaceId: req.params.id,
42
+ issueId: issue.id,
43
+ requirement: trimmedDescription,
44
+ description: trimmedDescription,
45
+ broadcast: (event, data) => broadcastToWorkspace(req.params.id, event, data),
46
+ });
47
+ }
36
48
  });
37
49
  router.get('/:issueId', (req, res) => {
38
50
  const issue = issueService.getById(req.params.id, req.params.issueId);
@@ -313,7 +325,7 @@ function mergeWorkflowMembers(members, workflowId) {
313
325
  return base;
314
326
  return Array.from(new Set([
315
327
  ...base,
316
- ...workflow.nodes.map((node) => node.data.agentConfigId).filter(Boolean),
328
+ ...workflow.nodes.filter((n) => n.type === 'agent').map((node) => node.data.agentConfigId).filter(Boolean),
317
329
  ]));
318
330
  }
319
331
  //# sourceMappingURL=issue.js.map
@@ -27,5 +27,15 @@ router.delete('/', (req, res) => {
27
27
  nc.clearAll(workspaceId);
28
28
  res.json({ ok: true });
29
29
  });
30
+ router.delete('/:notificationId', (req, res) => {
31
+ const workspaceId = req.params.id;
32
+ const notificationId = req.params.notificationId;
33
+ const removed = nc.removeNotification(workspaceId, notificationId);
34
+ if (!removed) {
35
+ res.status(404).json({ error: 'Not found' });
36
+ return;
37
+ }
38
+ res.json({ ok: true });
39
+ });
30
40
  export default router;
31
41
  //# sourceMappingURL=notification.js.map