@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,59 @@
1
+ import { runTitleGeneratorAgent } from '../agents/title-generator-agent.js';
2
+ import { getChannel, updateChannel } from './channel.js';
3
+ import * as issueService from './issue.js';
4
+ export function scheduleChannelTitleGeneration(input) {
5
+ if (!input.requirement.trim())
6
+ return;
7
+ void generateChannelTitle(input).catch((err) => {
8
+ console.error('[title-generator] channel title generation failed', {
9
+ workspaceId: input.workspaceId,
10
+ channelId: input.channelId,
11
+ error: err instanceof Error ? err.message : String(err),
12
+ });
13
+ });
14
+ }
15
+ export function scheduleIssueTitleGeneration(input) {
16
+ void generateIssueTitle(input).catch((err) => {
17
+ console.error('[title-generator] issue title generation failed', {
18
+ workspaceId: input.workspaceId,
19
+ issueId: input.issueId,
20
+ error: err instanceof Error ? err.message : String(err),
21
+ });
22
+ });
23
+ }
24
+ async function generateChannelTitle(input) {
25
+ const title = await runTitleGeneratorAgent({
26
+ workspaceId: input.workspaceId,
27
+ target: 'channel',
28
+ requirement: input.requirement,
29
+ });
30
+ const current = getChannel(input.workspaceId, input.channelId);
31
+ if (!current || current.name.trim() !== '')
32
+ return;
33
+ const updated = updateChannel(input.workspaceId, input.channelId, { name: title });
34
+ if (updated)
35
+ input.broadcast('channel.updated', updated);
36
+ }
37
+ async function generateIssueTitle(input) {
38
+ const title = await runTitleGeneratorAgent({
39
+ workspaceId: input.workspaceId,
40
+ target: 'issue',
41
+ requirement: input.requirement,
42
+ description: input.description,
43
+ });
44
+ const issue = issueService.getById(input.workspaceId, input.issueId);
45
+ if (!issue || issue.title.trim() !== '')
46
+ return;
47
+ issue.title = title;
48
+ const saved = issueService.save(input.workspaceId, issue);
49
+ input.broadcast('issue.updated', saved);
50
+ if (!saved.channelId)
51
+ return;
52
+ const channel = getChannel(input.workspaceId, saved.channelId);
53
+ if (!channel || channel.name.trim() !== '')
54
+ return;
55
+ const updatedChannel = updateChannel(input.workspaceId, saved.channelId, { name: title });
56
+ if (updatedChannel)
57
+ input.broadcast('channel.updated', updatedChannel);
58
+ }
59
+ //# sourceMappingURL=generated-title.js.map
@@ -1,18 +1,24 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { minimatch } from 'minimatch';
3
4
  const cache = new Map();
4
- function matchPattern(pattern, name) {
5
- if (pattern.startsWith('*.')) {
6
- return name.endsWith(pattern.slice(1));
5
+ function normalizeRelPath(path) {
6
+ return path.replace(/\\/g, '/').replace(/^\/+/, '');
7
+ }
8
+ function matchesDirPattern(pattern, relPath, hasSlash) {
9
+ if (!hasSlash) {
10
+ return relPath.split('/').includes(pattern);
7
11
  }
8
- if (pattern.startsWith('*.') && pattern.includes('/')) {
9
- return name.endsWith(pattern.split('/').pop());
12
+ return relPath === pattern || relPath.startsWith(`${pattern}/`);
13
+ }
14
+ function matchesPattern(pattern, relPath, name, isDir) {
15
+ if (pattern.dirOnly) {
16
+ return matchesDirPattern(pattern.pattern, relPath, pattern.hasSlash);
10
17
  }
11
- if (pattern.includes('*')) {
12
- const re = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
13
- return re.test(name);
18
+ if (!pattern.hasSlash) {
19
+ return minimatch(name, pattern.pattern, { dot: true, nocase: process.platform === 'win32' });
14
20
  }
15
- return name === pattern;
21
+ return minimatch(relPath, pattern.pattern, { dot: true, nocase: process.platform === 'win32' });
16
22
  }
17
23
  export async function createGitignoreFilter(rootDir) {
18
24
  const gitignorePath = join(rootDir, '.gitignore');
@@ -39,9 +45,8 @@ export async function createGitignoreFilter(rootDir) {
39
45
  const dirOnly = p.endsWith('/');
40
46
  if (dirOnly)
41
47
  p = p.slice(0, -1);
42
- // extract the last segment for name matching
43
- const namePart = p.includes('/') ? p.split('/').filter(Boolean).pop() : p;
44
- return { raw: namePart, dirOnly, negated, fullPath: p };
48
+ p = normalizeRelPath(p);
49
+ return { pattern: p, dirOnly, negated, hasSlash: p.includes('/') };
45
50
  });
46
51
  }
47
52
  catch {
@@ -49,14 +54,13 @@ export async function createGitignoreFilter(rootDir) {
49
54
  }
50
55
  const filter = {
51
56
  isIgnored(relPath, name, isDir) {
57
+ const normalizedRelPath = normalizeRelPath(relPath);
58
+ let ignored = false;
52
59
  for (const p of patterns) {
53
- if (p.dirOnly && !isDir)
54
- continue;
55
- if (matchPattern(p.raw, name) || matchPattern(p.fullPath, relPath)) {
56
- return !p.negated;
57
- }
60
+ if (matchesPattern(p, normalizedRelPath, name, isDir))
61
+ ignored = !p.negated;
58
62
  }
59
- return false;
63
+ return ignored;
60
64
  },
61
65
  };
62
66
  cache.set(rootDir, { mtime, filter });
@@ -0,0 +1,114 @@
1
+ // Workflow interaction manager — handles client-side UI interactions
2
+ // (alert/prompt/form/table_confirm) during workflow execution.
3
+ import { randomUUID } from 'node:crypto';
4
+ import { sendToClient, onClientConnected, onClientDisconnected, setInteractionResponseHandler } from '../ws/connection-manager.js';
5
+ const DEFAULT_TIMEOUT_MS = 5 * 60_000;
6
+ const RECONNECT_GRACE_MS = 30_000;
7
+ export class InteractionManager {
8
+ pending = new Map();
9
+ constructor() {
10
+ setInteractionResponseHandler((response, clientId) => {
11
+ this.handleResponse(response, clientId);
12
+ });
13
+ onClientConnected((clientId) => {
14
+ this.handleClientReconnect(clientId);
15
+ });
16
+ onClientDisconnected((clientId) => {
17
+ this.handleClientDisconnect(clientId);
18
+ });
19
+ }
20
+ async request(params) {
21
+ const id = randomUUID();
22
+ const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
23
+ const payload = {
24
+ id,
25
+ channel: 'workflow:interaction',
26
+ type: 'interaction_required',
27
+ executionId: params.executionId,
28
+ workflowId: params.workflowId,
29
+ nodeId: params.nodeId,
30
+ interactionType: params.interactionType,
31
+ schema: params.schema,
32
+ timeoutMs,
33
+ };
34
+ return new Promise((resolve, reject) => {
35
+ const timer = setTimeout(() => {
36
+ this.pending.delete(id);
37
+ reject(new Error(`Interaction timeout: ${params.interactionType}`));
38
+ }, timeoutMs);
39
+ this.pending.set(id, {
40
+ id,
41
+ clientId: params.clientId,
42
+ executionId: params.executionId,
43
+ workflowId: params.workflowId,
44
+ nodeId: params.nodeId,
45
+ interactionType: params.interactionType,
46
+ payload,
47
+ resolve,
48
+ reject,
49
+ timer,
50
+ });
51
+ const sent = sendToClient(params.clientId, payload);
52
+ if (!sent) {
53
+ clearTimeout(timer);
54
+ this.pending.delete(id);
55
+ reject(new Error(`Client not connected: ${params.interactionType}`));
56
+ }
57
+ });
58
+ }
59
+ cancelExecution(executionId, message = 'Execution stopped') {
60
+ let cancelled = 0;
61
+ for (const [id, pending] of this.pending.entries()) {
62
+ if (pending.executionId !== executionId)
63
+ continue;
64
+ this.pending.delete(id);
65
+ clearTimeout(pending.timer);
66
+ if (pending.reconnectTimer)
67
+ clearTimeout(pending.reconnectTimer);
68
+ pending.reject(new Error(message));
69
+ cancelled += 1;
70
+ }
71
+ return cancelled;
72
+ }
73
+ handleResponse(response, clientId) {
74
+ const pending = this.pending.get(response.id);
75
+ if (!pending || pending.clientId !== clientId)
76
+ return;
77
+ clearTimeout(pending.timer);
78
+ if (pending.reconnectTimer)
79
+ clearTimeout(pending.reconnectTimer);
80
+ this.pending.delete(response.id);
81
+ if (response.error) {
82
+ pending.reject(new Error(response.error.message));
83
+ return;
84
+ }
85
+ if (response.cancelled) {
86
+ pending.reject(new Error(`Interaction cancelled: ${pending.interactionType}`));
87
+ return;
88
+ }
89
+ pending.resolve(response.data);
90
+ }
91
+ handleClientDisconnect(clientId) {
92
+ for (const [id, pending] of this.pending.entries()) {
93
+ if (pending.clientId !== clientId || pending.reconnectTimer)
94
+ continue;
95
+ pending.reconnectTimer = setTimeout(() => {
96
+ this.pending.delete(id);
97
+ clearTimeout(pending.timer);
98
+ pending.reject(new Error(`Client disconnected: ${pending.interactionType}`));
99
+ }, RECONNECT_GRACE_MS);
100
+ }
101
+ }
102
+ handleClientReconnect(clientId) {
103
+ for (const pending of this.pending.values()) {
104
+ if (pending.clientId !== clientId)
105
+ continue;
106
+ if (pending.reconnectTimer) {
107
+ clearTimeout(pending.reconnectTimer);
108
+ pending.reconnectTimer = undefined;
109
+ }
110
+ sendToClient(clientId, pending.payload);
111
+ }
112
+ }
113
+ }
114
+ //# sourceMappingURL=interaction-manager.js.map
@@ -1,16 +1,20 @@
1
1
  import * as issueService from './issue.js';
2
2
  import * as taskService from './task.js';
3
+ import * as channelService from './channel.js';
4
+ import * as messageService from './message.js';
3
5
  import { scheduleRunnableIssueTasks } from '../agents/issue-task-controller.js';
4
6
  import { listWorkspaces } from '../storage/workspace-store.js';
5
7
  const RECOVERY_ERROR = 'Server restarted while task was running';
6
8
  export function recoverRunningWorkOnStartup(ctxFactory) {
7
9
  for (const workspace of listWorkspaces()) {
8
10
  const ctx = ctxFactory?.(workspace.id);
11
+ // 1. Mark running tasks as failed
9
12
  const failedTasks = taskService.markRunningTasksFailed(workspace.id, RECOVERY_ERROR);
10
13
  for (const task of failedTasks) {
11
14
  ctx?.broadcast('task.status_changed', { taskId: task.id, from: 'running', to: task.status });
12
15
  ctx?.broadcast('task.updated', task);
13
16
  }
17
+ // 2. Mark in_progress issues as error
14
18
  const inProgressIssues = issueService.list(workspace.id)
15
19
  .filter((issue) => issue.status === 'in_progress');
16
20
  for (const issue of inProgressIssues) {
@@ -20,6 +24,27 @@ export function recoverRunningWorkOnStartup(ctxFactory) {
20
24
  ctx?.broadcast('issue.status_changed', { issueId: issue.id, from: issue.status, to: 'error' });
21
25
  ctx?.broadcast('issue.updated', updated);
22
26
  }
27
+ // 3. Mark streaming/pending/waiting_for_user channel messages as error
28
+ const channels = channelService.listChannels(workspace.id);
29
+ for (const channel of channels) {
30
+ const messages = messageService.listMessages(workspace.id, channel.id);
31
+ for (const message of messages) {
32
+ if (message.status !== 'streaming' && message.status !== 'pending' && message.status !== 'waiting_for_user')
33
+ continue;
34
+ const updated = messageService.updateMessage(workspace.id, channel.id, message.id, {
35
+ status: 'error',
36
+ parts: message.parts?.map((part) => {
37
+ if ('status' in part && part.status === 'streaming') {
38
+ return { ...part, status: 'completed' };
39
+ }
40
+ return part;
41
+ }),
42
+ });
43
+ if (updated) {
44
+ ctx?.broadcast('channel.message.updated', updated);
45
+ }
46
+ }
47
+ }
23
48
  }
24
49
  }
25
50
  export async function retryErrorIssues(workspaceId, ctx) {
@@ -46,6 +46,15 @@ export function clearAll(workspaceId) {
46
46
  writeAll(workspaceId, []);
47
47
  broadcastToWorkspace(workspaceId, 'notification.cleared', null);
48
48
  }
49
+ export function removeNotification(workspaceId, notificationId) {
50
+ const all = readAll(workspaceId);
51
+ const idx = all.findIndex((n) => n.id === notificationId);
52
+ if (idx === -1)
53
+ return null;
54
+ const [removed] = all.splice(idx, 1);
55
+ writeAll(workspaceId, all);
56
+ return removed;
57
+ }
49
58
  export function unreadCount(workspaceId) {
50
59
  return readAll(workspaceId).filter((n) => !n.read).length;
51
60
  }
@@ -25,7 +25,14 @@ function writeMeta(meta) {
25
25
  writeFileSync(getMetaPath(), JSON.stringify(meta, null, 2), 'utf-8');
26
26
  }
27
27
  export function listOutputStyles() {
28
- return readMeta().templates;
28
+ const templates = readMeta().templates;
29
+ const agents = listTemplates();
30
+ return templates.map((tmpl) => ({
31
+ ...tmpl,
32
+ boundAgents: agents
33
+ .filter((a) => a.outputStyle === tmpl.name || a.outputStyle === tmpl.id)
34
+ .map((a) => ({ id: a.id, name: a.name || a.id, avatarUrl: a.avatarUrl })),
35
+ }));
29
36
  }
30
37
  export function resolveOutputStyleTemplate(ref) {
31
38
  const needle = sanitizeOutputStyleName(ref);
@@ -0,0 +1,257 @@
1
+ import { ensureDir, getDataDir, readJsonFile, writeJsonFile } from '../storage/json-store.js';
2
+ import { cpSync, existsSync, readFileSync, readdirSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { builtinModules } from 'node:module';
6
+ import vm from 'node:vm';
7
+ const require = createRequire(import.meta.url);
8
+ const STATE_FILE = () => path.join(pluginsDir(), 'state.json');
9
+ function templatesPluginsDir() {
10
+ const candidates = [
11
+ path.resolve(process.cwd(), 'packages/templates/plugins'),
12
+ path.resolve(process.cwd(), '../templates/plugins'),
13
+ path.resolve(process.cwd(), 'templates/plugins'),
14
+ ];
15
+ return candidates.find(candidate => existsSync(candidate)) || candidates[0];
16
+ }
17
+ function pluginsDir() {
18
+ return path.join(getDataDir(), 'plugins');
19
+ }
20
+ function pluginDir(pluginId) {
21
+ return path.join(pluginsDir(), pluginId);
22
+ }
23
+ function resolvePluginDir(pluginId) {
24
+ const direct = pluginDir(pluginId);
25
+ if (existsSync(direct))
26
+ return direct;
27
+ const root = pluginsDir();
28
+ if (!existsSync(root))
29
+ return null;
30
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
31
+ if (!entry.isDirectory())
32
+ continue;
33
+ const dir = path.join(root, entry.name);
34
+ const manifest = readManifestFromDir(dir);
35
+ if ((manifest?.id || entry.name) === pluginId)
36
+ return dir;
37
+ }
38
+ return null;
39
+ }
40
+ function readState() {
41
+ return readJsonFile(STATE_FILE()) ?? { enabled: {}, config: {} };
42
+ }
43
+ function writeState(state) {
44
+ writeJsonFile(STATE_FILE(), state);
45
+ }
46
+ function readManifestFromDir(dir) {
47
+ const candidates = ['plugin.json', 'manifest.json', 'info.json', 'web-plugin.json', 'package.json'];
48
+ for (const filename of candidates) {
49
+ const manifest = readJsonFile(path.join(dir, filename));
50
+ if (manifest?.id || manifest?.name)
51
+ return manifest;
52
+ }
53
+ return null;
54
+ }
55
+ function normalizePlugin(dirName, manifest, state) {
56
+ const id = String(manifest.id || dirName);
57
+ return {
58
+ id,
59
+ name: String(manifest.name || id),
60
+ version: String(manifest.version || '0.0.0'),
61
+ description: String(manifest.description || ''),
62
+ author: manifest.author || { name: 'Unknown' },
63
+ tags: Array.isArray(manifest.tags) ? manifest.tags : [],
64
+ hasView: Boolean(manifest.hasView),
65
+ hasWorkflow: Boolean(manifest.hasWorkflow || manifest.workflowNodes?.length || manifest.entries?.workflow),
66
+ type: manifest.type,
67
+ enabled: state.enabled[id] ?? Boolean(manifest.enabled),
68
+ config: Array.isArray(manifest.config) ? manifest.config : [],
69
+ iconPath: manifest.iconPath || '',
70
+ };
71
+ }
72
+ function getManifest(pluginId) {
73
+ const dir = resolvePluginDir(pluginId);
74
+ return dir ? readManifestFromDir(dir) : null;
75
+ }
76
+ function createRequireStub() {
77
+ const target = () => createRequireStub();
78
+ return new Proxy(target, {
79
+ get: (_target, prop) => {
80
+ if (prop === 'then')
81
+ return undefined;
82
+ if (prop === Symbol.toPrimitive)
83
+ return () => '';
84
+ return createRequireStub();
85
+ },
86
+ apply: () => createRequireStub(),
87
+ construct: () => createRequireStub(),
88
+ });
89
+ }
90
+ function loadCommonJsWorkflowNodes(workflowPath) {
91
+ const source = readFileSync(workflowPath, 'utf-8');
92
+ const module = { exports: {} };
93
+ const localRequire = (request) => {
94
+ const normalized = request.replace(/^node:/, '');
95
+ if (builtinModules.includes(normalized))
96
+ return require(request);
97
+ return createRequireStub();
98
+ };
99
+ const script = new vm.Script(`(function(require, module, exports, __filename, __dirname) {\n${source}\n})`, { filename: workflowPath });
100
+ const runner = script.runInNewContext({ console, Buffer, URL, URLSearchParams, setTimeout, clearTimeout });
101
+ runner(localRequire, module, module.exports, workflowPath, path.dirname(workflowPath));
102
+ const payload = module.exports;
103
+ if (Array.isArray(payload))
104
+ return payload;
105
+ return Array.isArray(payload?.nodes) ? payload.nodes : [];
106
+ }
107
+ function loadCommonJsWorkflowModule(workflowPath) {
108
+ const source = readFileSync(workflowPath, 'utf-8');
109
+ const module = { exports: {} };
110
+ const localRequire = createRequire(workflowPath);
111
+ const script = new vm.Script(`(function(require, module, exports, __filename, __dirname) {\n${source}\n})`, { filename: workflowPath });
112
+ const runner = script.runInNewContext({ console, Buffer, URL, URLSearchParams, setTimeout, clearTimeout });
113
+ runner(localRequire, module, module.exports, workflowPath, path.dirname(workflowPath));
114
+ const payload = module.exports;
115
+ const rawNodes = Array.isArray(payload) ? payload : Array.isArray(payload?.nodes) ? payload.nodes : [];
116
+ const handlers = new Map();
117
+ const nodes = rawNodes.map((node) => {
118
+ const { handler, ...serializable } = node;
119
+ if (typeof handler === 'function' && typeof serializable.type === 'string') {
120
+ handlers.set(serializable.type, handler);
121
+ }
122
+ return serializable;
123
+ });
124
+ return { nodes, handlers };
125
+ }
126
+ export function listPlugins() {
127
+ const root = pluginsDir();
128
+ ensureDir(root);
129
+ const state = readState();
130
+ return readdirSync(root, { withFileTypes: true })
131
+ .filter(entry => entry.isDirectory())
132
+ .map((entry) => {
133
+ const manifest = readManifestFromDir(path.join(root, entry.name));
134
+ return manifest ? normalizePlugin(entry.name, manifest, state) : null;
135
+ })
136
+ .filter((plugin) => Boolean(plugin));
137
+ }
138
+ export function listWorkflowPlugins() {
139
+ return listPlugins().filter(plugin => plugin.hasWorkflow);
140
+ }
141
+ export function setPluginEnabled(pluginId, enabled) {
142
+ const state = readState();
143
+ state.enabled[pluginId] = enabled;
144
+ writeState(state);
145
+ const plugin = listPlugins().find(item => item.id === pluginId);
146
+ if (!plugin)
147
+ throw new Error('Plugin not found');
148
+ return plugin;
149
+ }
150
+ function findTemplatePluginDir(pluginId) {
151
+ const root = templatesPluginsDir();
152
+ if (!existsSync(root))
153
+ return null;
154
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
155
+ if (!entry.isDirectory())
156
+ continue;
157
+ const dir = path.join(root, entry.name);
158
+ const manifest = readManifestFromDir(dir);
159
+ if ((manifest?.id || entry.name) === pluginId)
160
+ return dir;
161
+ }
162
+ return null;
163
+ }
164
+ export function installTemplatePlugin(pluginId) {
165
+ const sourceDir = findTemplatePluginDir(pluginId);
166
+ if (!sourceDir)
167
+ throw new Error('Template plugin not found');
168
+ const manifest = readManifestFromDir(sourceDir);
169
+ const id = String(manifest?.id || path.basename(sourceDir));
170
+ const targetDir = path.join(pluginsDir(), path.basename(sourceDir));
171
+ ensureDir(pluginsDir());
172
+ if (!existsSync(targetDir)) {
173
+ cpSync(sourceDir, targetDir, {
174
+ recursive: true,
175
+ filter: (src) => !src.split(path.sep).includes('node_modules'),
176
+ });
177
+ }
178
+ return setPluginEnabled(id, true);
179
+ }
180
+ export function getPluginConfig(pluginId) {
181
+ const state = readState();
182
+ const manifest = getManifest(pluginId);
183
+ const defaults = {};
184
+ for (const field of manifest?.config || []) {
185
+ defaults[field.key] = field.value ?? '';
186
+ }
187
+ return { ...defaults, ...(state.config[pluginId] || {}) };
188
+ }
189
+ export function savePluginConfig(pluginId, data) {
190
+ const state = readState();
191
+ state.config[pluginId] = data;
192
+ writeState(state);
193
+ return { success: true };
194
+ }
195
+ function getWorkflowEntryPath(pluginId) {
196
+ const manifest = getManifest(pluginId);
197
+ if (!manifest)
198
+ return null;
199
+ const dir = resolvePluginDir(pluginId);
200
+ if (!dir)
201
+ return null;
202
+ const workflowEntry = manifest.entries?.workflow || 'workflow.json';
203
+ const workflowPath = path.join(dir, workflowEntry);
204
+ return existsSync(workflowPath) ? workflowPath : null;
205
+ }
206
+ export function getWorkflowNodes(pluginId) {
207
+ const manifest = getManifest(pluginId);
208
+ if (!manifest)
209
+ throw new Error('Plugin not found');
210
+ if (Array.isArray(manifest.workflowNodes))
211
+ return manifest.workflowNodes;
212
+ const workflowPath = getWorkflowEntryPath(pluginId);
213
+ if (!workflowPath)
214
+ return [];
215
+ if (workflowPath.endsWith('.js') || workflowPath.endsWith('.cjs')) {
216
+ return loadCommonJsWorkflowNodes(workflowPath);
217
+ }
218
+ const payload = readJsonFile(workflowPath);
219
+ if (Array.isArray(payload))
220
+ return payload;
221
+ return Array.isArray(payload?.nodes) ? payload.nodes : [];
222
+ }
223
+ function getExecutablePluginByNodeType(nodeType) {
224
+ for (const plugin of listWorkflowPlugins()) {
225
+ if (!plugin.enabled)
226
+ continue;
227
+ const workflowPath = getWorkflowEntryPath(plugin.id);
228
+ if (!workflowPath || (!workflowPath.endsWith('.js') && !workflowPath.endsWith('.cjs')))
229
+ continue;
230
+ try {
231
+ const { handlers } = loadCommonJsWorkflowModule(workflowPath);
232
+ const handler = handlers.get(nodeType);
233
+ if (handler)
234
+ return { plugin, handler };
235
+ }
236
+ catch {
237
+ continue;
238
+ }
239
+ }
240
+ return null;
241
+ }
242
+ export function canExecuteWorkflowNode(nodeType) {
243
+ return Boolean(getExecutablePluginByNodeType(nodeType));
244
+ }
245
+ export async function executeWorkflowNode(nodeType, args, hooks) {
246
+ const executable = getExecutablePluginByNodeType(nodeType);
247
+ if (!executable)
248
+ throw new Error(`Plugin node is not enabled or has no executable handler: ${nodeType}`);
249
+ return executable.handler({
250
+ api: {},
251
+ nodeId: '',
252
+ nodeLabel: nodeType,
253
+ upstream: {},
254
+ logger: hooks.logger,
255
+ }, args);
256
+ }
257
+ //# sourceMappingURL=plugin.js.map
@@ -24,7 +24,14 @@ function writeMeta(meta) {
24
24
  writeFileSync(getMetaPath(), JSON.stringify(meta, null, 2), 'utf-8');
25
25
  }
26
26
  export function listPromptTemplates() {
27
- return readMeta().templates;
27
+ const templates = readMeta().templates;
28
+ const agents = listTemplates();
29
+ return templates.map((tmpl) => ({
30
+ ...tmpl,
31
+ boundAgents: agents
32
+ .filter((a) => a.systemPrompt === tmpl.content)
33
+ .map((a) => ({ id: a.id, name: a.name || a.id, avatarUrl: a.avatarUrl })),
34
+ }));
28
35
  }
29
36
  export function createPromptTemplate(name, content, storeId) {
30
37
  const meta = readMeta();
@@ -3,9 +3,17 @@ import { v4 as uuid } from 'uuid';
3
3
  import { accessSync, constants } from 'node:fs';
4
4
  const MAX_BUFFER_LINES = 1000;
5
5
  const DEFAULT_READ_LIMIT = 100;
6
+ const DEBUG_TERMINAL_DUP = '[DEBUG-terminal-dup]';
6
7
  const sessions = new Map();
7
8
  export function createSession(workspaceId, cwd, onOutput, onExit, shell, env, sessionId) {
8
9
  const id = sessionId || uuid();
10
+ console.log(DEBUG_TERMINAL_DUP, 'server pty.createSession start', {
11
+ workspaceId,
12
+ sessionId: id,
13
+ cwd,
14
+ shell,
15
+ existingSessionIds: Array.from(sessions.keys()),
16
+ });
9
17
  if (sessions.has(id)) {
10
18
  throw new Error(`Terminal session already exists: ${id}`);
11
19
  }
@@ -26,6 +34,12 @@ export function createSession(workspaceId, cwd, onOutput, onExit, shell, env, se
26
34
  });
27
35
  const session = { id, pty: ptyProcess, workspaceId, cwd, shell: resolvedShell, buffer: [], output: '' };
28
36
  ptyProcess.onData((data) => {
37
+ console.log(DEBUG_TERMINAL_DUP, 'server pty.onData', {
38
+ workspaceId,
39
+ sessionId: id,
40
+ outputLength: data.length,
41
+ preview: data.slice(0, 80),
42
+ });
29
43
  session.buffer.push(data);
30
44
  if (session.buffer.length > MAX_BUFFER_LINES) {
31
45
  session.buffer = session.buffer.slice(-MAX_BUFFER_LINES);
@@ -35,6 +49,12 @@ export function createSession(workspaceId, cwd, onOutput, onExit, shell, env, se
35
49
  });
36
50
  ptyProcess.onExit(({ exitCode }) => onExit(id, exitCode ?? 0));
37
51
  sessions.set(id, session);
52
+ console.log(DEBUG_TERMINAL_DUP, 'server pty.createSession stored', {
53
+ workspaceId,
54
+ sessionId: id,
55
+ resolvedShell,
56
+ sessionCount: sessions.size,
57
+ });
38
58
  return id;
39
59
  }
40
60
  export function write(sessionId, data) {