agentquad 0.3.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -0
  3. package/dist-web/assets/index-CMaXwixo.js +1234 -0
  4. package/dist-web/assets/index-DBHApzV1.css +32 -0
  5. package/dist-web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  6. package/dist-web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  7. package/dist-web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  8. package/dist-web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  9. package/dist-web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  10. package/dist-web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  11. package/dist-web/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
  12. package/dist-web/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
  13. package/dist-web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  14. package/dist-web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  15. package/dist-web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  16. package/dist-web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  17. package/dist-web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  18. package/dist-web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  19. package/dist-web/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
  20. package/dist-web/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
  21. package/dist-web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  22. package/dist-web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  23. package/dist-web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  24. package/dist-web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  25. package/dist-web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  26. package/dist-web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  27. package/dist-web/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
  28. package/dist-web/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
  29. package/dist-web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  30. package/dist-web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  31. package/dist-web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  32. package/dist-web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  33. package/dist-web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  34. package/dist-web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  35. package/dist-web/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
  36. package/dist-web/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
  37. package/dist-web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  38. package/dist-web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  39. package/dist-web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  40. package/dist-web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  41. package/dist-web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  42. package/dist-web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  43. package/dist-web/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
  44. package/dist-web/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  45. package/dist-web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  46. package/dist-web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  47. package/dist-web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  48. package/dist-web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  49. package/dist-web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  50. package/dist-web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  51. package/dist-web/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
  52. package/dist-web/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
  53. package/dist-web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  54. package/dist-web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  55. package/dist-web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  56. package/dist-web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  57. package/dist-web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  58. package/dist-web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  59. package/dist-web/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
  60. package/dist-web/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
  61. package/dist-web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  62. package/dist-web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  63. package/dist-web/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
  64. package/dist-web/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
  65. package/dist-web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  66. package/dist-web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  67. package/dist-web/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
  68. package/dist-web/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
  69. package/dist-web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  70. package/dist-web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  71. package/dist-web/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  72. package/dist-web/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  73. package/dist-web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  74. package/dist-web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  75. package/dist-web/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
  76. package/dist-web/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
  77. package/dist-web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  78. package/dist-web/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
  79. package/dist-web/assets/logo-D4DDtU-r.png +0 -0
  80. package/dist-web/favicon.png +0 -0
  81. package/dist-web/index.html +14 -0
  82. package/package.json +88 -0
  83. package/src/ask-user-buttons.js +142 -0
  84. package/src/claude-transcript.js +203 -0
  85. package/src/cli.js +1040 -0
  86. package/src/codex-event-emitter.js +111 -0
  87. package/src/codex-prompt-detector.js +53 -0
  88. package/src/codex-sidecar.js +52 -0
  89. package/src/codex-transcript.js +74 -0
  90. package/src/config.js +692 -0
  91. package/src/data/claude-code-commands.json +52 -0
  92. package/src/db.js +1503 -0
  93. package/src/dispatch.js +13 -0
  94. package/src/export/todoMarkdown.js +246 -0
  95. package/src/first-run-wizard.js +82 -0
  96. package/src/git/gitStatus.js +139 -0
  97. package/src/lark-api-client.js +205 -0
  98. package/src/lark-bot.js +510 -0
  99. package/src/lark-card.js +88 -0
  100. package/src/lark-config-service.js +16 -0
  101. package/src/lark-event-client.js +107 -0
  102. package/src/lark-image.js +99 -0
  103. package/src/lark-markdown.js +51 -0
  104. package/src/lark-video.js +163 -0
  105. package/src/mcp/audit.js +34 -0
  106. package/src/mcp/server.js +83 -0
  107. package/src/mcp/tools/destructive/index.js +252 -0
  108. package/src/mcp/tools/openclaw/index.js +405 -0
  109. package/src/mcp/tools/read/index.js +269 -0
  110. package/src/mcp/tools/write/index.js +157 -0
  111. package/src/openclaw-bridge.js +566 -0
  112. package/src/openclaw-hook-installer.js +338 -0
  113. package/src/openclaw-hook.js +908 -0
  114. package/src/openclaw-wizard.js +2442 -0
  115. package/src/pending-questions.js +297 -0
  116. package/src/pricing.js +45 -0
  117. package/src/prompt-render.js +36 -0
  118. package/src/pty.js +992 -0
  119. package/src/routes/ai-terminal.js +1228 -0
  120. package/src/routes/git.js +89 -0
  121. package/src/routes/openclaw-hook.js +67 -0
  122. package/src/routes/openclaw-inbound.js +36 -0
  123. package/src/routes/recurringRules.js +80 -0
  124. package/src/routes/reports.js +50 -0
  125. package/src/routes/search.js +46 -0
  126. package/src/routes/stats.js +31 -0
  127. package/src/routes/telegram-config.js +152 -0
  128. package/src/routes/telegram-sync.js +221 -0
  129. package/src/routes/templates.js +63 -0
  130. package/src/routes/todos.js +649 -0
  131. package/src/routes/transcripts.js +75 -0
  132. package/src/routes/uploads.js +107 -0
  133. package/src/routes/wiki.js +142 -0
  134. package/src/search/fts.js +209 -0
  135. package/src/search/index.js +199 -0
  136. package/src/search/transcripts.js +148 -0
  137. package/src/server.js +1791 -0
  138. package/src/session-input-dispatcher.js +256 -0
  139. package/src/stats/markdown.js +42 -0
  140. package/src/stats/report.js +207 -0
  141. package/src/summarize.js +84 -0
  142. package/src/system-rules.js +52 -0
  143. package/src/telegram-bot.js +875 -0
  144. package/src/telegram-commands.js +149 -0
  145. package/src/telegram-config-service.js +84 -0
  146. package/src/telegram-image.js +95 -0
  147. package/src/telegram-loading-status.js +112 -0
  148. package/src/telegram-markdown.js +82 -0
  149. package/src/telegram-reaction-tracker.js +69 -0
  150. package/src/telegram-video.js +75 -0
  151. package/src/templates/claude-hooks/notify.js +103 -0
  152. package/src/transcript.js +305 -0
  153. package/src/transcripts/blocks.js +56 -0
  154. package/src/transcripts/index.js +222 -0
  155. package/src/transcripts/indexer.js +34 -0
  156. package/src/transcripts/matcher.js +70 -0
  157. package/src/transcripts/scanner.js +259 -0
  158. package/src/usage-footer.js +170 -0
  159. package/src/usage-parser.js +132 -0
  160. package/src/wiki/guide.js +44 -0
  161. package/src/wiki/index.js +232 -0
  162. package/src/wiki/redact.js +34 -0
  163. package/src/wiki/sources.js +122 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * POST /api/telegram-sync (别名:POST /api/sync)
3
+ * body: { dryRun?: boolean }
4
+ *
5
+ * 把 Telegram topic / Lark thread、bridge 内存路由、ai-terminal sessions、DB todos 多方对齐。
6
+ *
7
+ * 检测的不一致(每条 action 带 channel:'telegram' | 'lark' | null):
8
+ * 1. open_topic ← (telegram) todo 'ai_running' + PTY 真活着 + 没绑 telegram topic
9
+ * 2. close_topic ← (telegram) 绑了 telegram topic 但 PTY 已死 (todo 还没 done)
10
+ * 3. open_thread ← (lark) PTY 真活着 + 没绑 lark thread
11
+ * 4. close_thread ← (lark) 绑了 lark thread 但 PTY 已死 (todo 还没 done)
12
+ * 5. clear_route ← bridge 路由的 sessionId 已不在 ait.sessions(孤儿,channel 无关)
13
+ *
14
+ * dryRun=true 只返回计划,不动手;false 实际执行并返回每一步的结果。
15
+ */
16
+ import { Router } from 'express'
17
+
18
+ export function createTelegramSyncRouter({ db, aiTerminal, openclaw, wizard, getConfig } = {}) {
19
+ if (!db || !aiTerminal || !openclaw || !wizard) {
20
+ throw new Error('telegram-sync: missing deps')
21
+ }
22
+ const router = Router()
23
+
24
+ function isAlive(sess) {
25
+ return sess && (sess.status === 'running' || sess.status === 'idle' || sess.status === 'pending_confirm')
26
+ }
27
+
28
+ // 默认两个 channel 都启用;配置里显式 enabled === false 才视为禁用
29
+ function readChannelFlags() {
30
+ const cfg = (typeof getConfig === 'function' ? getConfig() : null) || {}
31
+ return {
32
+ telegramEnabled: cfg.telegram?.enabled !== false,
33
+ larkEnabled: cfg.lark?.enabled !== false,
34
+ }
35
+ }
36
+
37
+ function bridgeIsLark(route) {
38
+ return route?.channel === 'lark' && !!route?.rootMessageId
39
+ }
40
+ // bridge route 没显式 channel 字段时按老语义当 telegram(向后兼容)
41
+ function bridgeIsTelegram(route) {
42
+ return !!route?.threadId && route?.channel !== 'lark'
43
+ }
44
+
45
+ function planSync() {
46
+ const actions = []
47
+ const { telegramEnabled, larkEnabled } = readChannelFlags()
48
+
49
+ // 1) 遍历 todos:分别对 telegram 和 lark 路由独立判断(一个 session 可能两边都绑)
50
+ const todos = db.listTodos({ status: 'all', archived: 'all' }) || []
51
+ for (const t of todos) {
52
+ const sessions = (t.aiSessions || []).filter(Boolean)
53
+ if (!sessions.length) continue
54
+ // 取最近一条
55
+ const aiSess = sessions[0]
56
+ const sid = aiSess.sessionId
57
+ if (!sid) continue
58
+ const liveSess = aiTerminal.sessions.get(sid)
59
+ const bridgeRoute = openclaw.resolveRoute?.(sid) || null
60
+
61
+ // ── Telegram 分支 ──
62
+ if (telegramEnabled) {
63
+ const dbHasTgRoute = !!aiSess.telegramRoute?.threadId
64
+ const bridgeHasTgRoute = bridgeIsTelegram(bridgeRoute)
65
+ if (isAlive(liveSess)) {
66
+ if (!dbHasTgRoute && !bridgeHasTgRoute) {
67
+ actions.push({
68
+ type: 'open_topic',
69
+ channel: 'telegram',
70
+ todoId: t.id,
71
+ todoTitle: t.title,
72
+ sessionId: sid,
73
+ reason: 'live_session_no_topic',
74
+ })
75
+ }
76
+ } else if ((dbHasTgRoute || bridgeHasTgRoute) && t.status !== 'done') {
77
+ const tgRoute = aiSess.telegramRoute || (bridgeHasTgRoute ? bridgeRoute : null)
78
+ if (tgRoute) {
79
+ actions.push({
80
+ type: 'close_topic',
81
+ channel: 'telegram',
82
+ todoId: t.id,
83
+ todoTitle: t.title,
84
+ sessionId: sid,
85
+ chatId: String(tgRoute.targetUserId),
86
+ threadId: Number(tgRoute.threadId),
87
+ reason: 'session_dead_topic_open',
88
+ })
89
+ }
90
+ }
91
+ }
92
+
93
+ // ── Lark 分支 ──
94
+ if (larkEnabled) {
95
+ const dbHasLarkRoute = !!aiSess.larkRoute?.rootMessageId
96
+ const bridgeHasLarkRoute = bridgeIsLark(bridgeRoute)
97
+ if (isAlive(liveSess)) {
98
+ if (!dbHasLarkRoute && !bridgeHasLarkRoute) {
99
+ actions.push({
100
+ type: 'open_thread',
101
+ channel: 'lark',
102
+ todoId: t.id,
103
+ todoTitle: t.title,
104
+ sessionId: sid,
105
+ reason: 'live_session_no_lark_thread',
106
+ })
107
+ }
108
+ } else if ((dbHasLarkRoute || bridgeHasLarkRoute) && t.status !== 'done') {
109
+ const larkRoute = aiSess.larkRoute || (bridgeHasLarkRoute ? bridgeRoute : null)
110
+ if (larkRoute) {
111
+ actions.push({
112
+ type: 'close_thread',
113
+ channel: 'lark',
114
+ todoId: t.id,
115
+ todoTitle: t.title,
116
+ sessionId: sid,
117
+ chatId: String(larkRoute.targetUserId),
118
+ rootMessageId: String(larkRoute.rootMessageId),
119
+ reason: 'session_dead_lark_thread_open',
120
+ })
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ // 2) 遍历 bridge 内存路由:找孤儿(sessionId 已不存在于 ait.sessions)
127
+ const routes = openclaw.listSessionRoutes?.() || []
128
+ for (const r of routes) {
129
+ if (!aiTerminal.sessions.has(r.sessionId)) {
130
+ // 已经在上面 close 列表里的就不重复(避免双触发)
131
+ const dup = actions.find((a) =>
132
+ a.sessionId === r.sessionId && (a.type === 'close_topic' || a.type === 'close_thread'),
133
+ )
134
+ if (dup) continue
135
+ actions.push({
136
+ type: 'clear_route',
137
+ channel: r.channel === 'lark' ? 'lark' : (r.channel || null),
138
+ sessionId: r.sessionId,
139
+ chatId: r.targetUserId,
140
+ threadId: r.threadId || null,
141
+ rootMessageId: r.rootMessageId || null,
142
+ reason: 'orphan_route',
143
+ })
144
+ }
145
+ }
146
+
147
+ return actions
148
+ }
149
+
150
+ async function executeSync(plan) {
151
+ const results = []
152
+ for (const a of plan) {
153
+ try {
154
+ if (a.type === 'open_topic') {
155
+ const r = await wizard.ensureTopicForSession({
156
+ sessionId: a.sessionId,
157
+ todoId: a.todoId,
158
+ })
159
+ results.push({ ...a, result: r })
160
+ } else if (a.type === 'close_topic') {
161
+ const r = await wizard.handleTopicEvent({
162
+ type: 'closed',
163
+ chatId: a.chatId,
164
+ threadId: a.threadId,
165
+ })
166
+ results.push({ ...a, result: r })
167
+ } else if (a.type === 'open_thread') {
168
+ const r = await wizard.ensureLarkThreadForSession({
169
+ sessionId: a.sessionId,
170
+ todoId: a.todoId,
171
+ })
172
+ results.push({ ...a, result: r })
173
+ } else if (a.type === 'close_thread') {
174
+ const r = await wizard.handleLarkThreadClose({
175
+ chatId: a.chatId,
176
+ rootMessageId: a.rootMessageId,
177
+ })
178
+ results.push({ ...a, result: r })
179
+ } else if (a.type === 'clear_route') {
180
+ openclaw.clearSessionRoute?.(a.sessionId, 'sync-clear')
181
+ results.push({ ...a, result: { ok: true, action: 'cleared' } })
182
+ } else {
183
+ results.push({ ...a, result: { ok: false, reason: 'unknown_action' } })
184
+ }
185
+ } catch (e) {
186
+ results.push({ ...a, result: { ok: false, error: e?.message || 'unknown' } })
187
+ }
188
+ }
189
+ return results
190
+ }
191
+
192
+ router.post('/', async (req, res) => {
193
+ try {
194
+ const dryRun = !!(req.body && req.body.dryRun)
195
+ const plan = planSync()
196
+ const summary = {
197
+ total: plan.length,
198
+ open_topic: plan.filter((a) => a.type === 'open_topic').length,
199
+ close_topic: plan.filter((a) => a.type === 'close_topic').length,
200
+ open_thread: plan.filter((a) => a.type === 'open_thread').length,
201
+ close_thread: plan.filter((a) => a.type === 'close_thread').length,
202
+ clear_route: plan.filter((a) => a.type === 'clear_route').length,
203
+ }
204
+ if (dryRun) {
205
+ return res.json({ ok: true, dryRun: true, summary, actions: plan })
206
+ }
207
+ const results = await executeSync(plan)
208
+ const okCount = results.filter((r) => r.result?.ok).length
209
+ return res.json({
210
+ ok: true,
211
+ dryRun: false,
212
+ summary: { ...summary, succeeded: okCount, failed: results.length - okCount },
213
+ actions: results,
214
+ })
215
+ } catch (e) {
216
+ return res.status(500).json({ ok: false, error: e?.message || 'sync_failed' })
217
+ }
218
+ })
219
+
220
+ return { router, __test__: { planSync, executeSync } }
221
+ }
@@ -0,0 +1,63 @@
1
+ import { Router } from 'express'
2
+
3
+ export function createTemplatesRouter({ db }) {
4
+ const router = Router()
5
+
6
+ router.get('/', (req, res) => {
7
+ try {
8
+ res.json({ ok: true, list: db.listTemplates() })
9
+ } catch (e) {
10
+ res.status(500).json({ ok: false, error: e.message })
11
+ }
12
+ })
13
+
14
+ router.post('/', (req, res) => {
15
+ try {
16
+ const { name, description, content, sortOrder } = req.body || {}
17
+ if (!name || typeof name !== 'string') {
18
+ res.status(400).json({ ok: false, error: 'missing name' })
19
+ return
20
+ }
21
+ if (typeof content !== 'string') {
22
+ res.status(400).json({ ok: false, error: 'missing content' })
23
+ return
24
+ }
25
+ const tpl = db.createTemplate({ name: name.trim(), description: description || '', content, sortOrder })
26
+ res.json({ ok: true, template: tpl })
27
+ } catch (e) {
28
+ res.status(500).json({ ok: false, error: e.message })
29
+ }
30
+ })
31
+
32
+ router.put('/:id', (req, res) => {
33
+ try {
34
+ const tpl = db.updateTemplate(req.params.id, req.body || {})
35
+ if (!tpl) {
36
+ res.status(404).json({ ok: false, error: 'not_found' })
37
+ return
38
+ }
39
+ res.json({ ok: true, template: tpl })
40
+ } catch (e) {
41
+ if (e.message === 'builtin_template_readonly') {
42
+ res.status(400).json({ ok: false, error: '内置模板不可编辑,请先复制再修改' })
43
+ return
44
+ }
45
+ res.status(500).json({ ok: false, error: e.message })
46
+ }
47
+ })
48
+
49
+ router.delete('/:id', (req, res) => {
50
+ try {
51
+ db.deleteTemplate(req.params.id)
52
+ res.json({ ok: true })
53
+ } catch (e) {
54
+ if (e.message === 'builtin_template_readonly') {
55
+ res.status(400).json({ ok: false, error: '内置模板不可删除' })
56
+ return
57
+ }
58
+ res.status(500).json({ ok: false, error: e.message })
59
+ }
60
+ })
61
+
62
+ return router
63
+ }