mulmoclaude 0.1.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 (408) hide show
  1. package/README.md +44 -0
  2. package/bin/mulmoclaude.js +202 -0
  3. package/bin/prepare-dist.js +93 -0
  4. package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +1 -0
  5. package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +5 -0
  6. package/client/assets/index-D8rhwXLq.js +4906 -0
  7. package/client/assets/index-KNLBjwuh.css +1 -0
  8. package/client/assets/index.es-D4YyL_Dg-BfRHLTZV.js +5 -0
  9. package/client/assets/material-icons-Dr0goTwe.woff +0 -0
  10. package/client/assets/material-icons-kAwBdRge.woff2 +0 -0
  11. package/client/assets/material-icons-outlined-BpWbwl2n.woff +0 -0
  12. package/client/assets/material-icons-outlined-DZhiGvEA.woff2 +0 -0
  13. package/client/assets/material-icons-round-BDlwx-sv.woff +0 -0
  14. package/client/assets/material-icons-round-DrirKXBx.woff2 +0 -0
  15. package/client/assets/material-icons-sharp-CH1KkVu7.woff +0 -0
  16. package/client/assets/material-icons-sharp-gidztirS.woff2 +0 -0
  17. package/client/assets/material-icons-two-tone-B7wz7mED.woff +0 -0
  18. package/client/assets/material-icons-two-tone-DuNIpaEj.woff2 +0 -0
  19. package/client/assets/mulmo_bw-ERmkSv0a.png +0 -0
  20. package/client/assets/purify.es-Fx1Nqyry-PeS5RUhs.js +2 -0
  21. package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +1 -0
  22. package/client/index.html +28 -0
  23. package/package.json +66 -0
  24. package/server/agent/attachmentConverter.ts +270 -0
  25. package/server/agent/config.ts +414 -0
  26. package/server/agent/index.ts +260 -0
  27. package/server/agent/mcp-server.ts +412 -0
  28. package/server/agent/mcp-tools/index.ts +63 -0
  29. package/server/agent/mcp-tools/x.ts +188 -0
  30. package/server/agent/plugin-names.ts +75 -0
  31. package/server/agent/prompt.ts +349 -0
  32. package/server/agent/resumeFailover.ts +129 -0
  33. package/server/agent/sandboxMounts.ts +329 -0
  34. package/server/agent/stream.ts +194 -0
  35. package/server/api/auth/bearerAuth.ts +61 -0
  36. package/server/api/auth/token.ts +98 -0
  37. package/server/api/csrfGuard.ts +85 -0
  38. package/server/api/routes/agent.ts +478 -0
  39. package/server/api/routes/chart.ts +98 -0
  40. package/server/api/routes/chat-index.ts +46 -0
  41. package/server/api/routes/config.ts +258 -0
  42. package/server/api/routes/dispatchResponse.ts +79 -0
  43. package/server/api/routes/files.ts +812 -0
  44. package/server/api/routes/html.ts +101 -0
  45. package/server/api/routes/image.ts +169 -0
  46. package/server/api/routes/mulmo-script.ts +712 -0
  47. package/server/api/routes/mulmoScriptValidate.ts +101 -0
  48. package/server/api/routes/notifications.ts +69 -0
  49. package/server/api/routes/pdf.ts +163 -0
  50. package/server/api/routes/plugins.ts +276 -0
  51. package/server/api/routes/presentHtml.ts +48 -0
  52. package/server/api/routes/roles.ts +125 -0
  53. package/server/api/routes/scheduler.ts +153 -0
  54. package/server/api/routes/schedulerHandlers.ts +151 -0
  55. package/server/api/routes/schedulerTasks.ts +163 -0
  56. package/server/api/routes/sessions.ts +294 -0
  57. package/server/api/routes/sessionsCursor.ts +59 -0
  58. package/server/api/routes/skills.ts +195 -0
  59. package/server/api/routes/sources.ts +540 -0
  60. package/server/api/routes/todos.ts +263 -0
  61. package/server/api/routes/todosColumnsHandlers.ts +347 -0
  62. package/server/api/routes/todosHandlers.ts +274 -0
  63. package/server/api/routes/todosItemsHandlers.ts +386 -0
  64. package/server/api/routes/wiki/pageIndex.ts +53 -0
  65. package/server/api/routes/wiki.ts +363 -0
  66. package/server/api/sandboxStatus.ts +64 -0
  67. package/server/events/notifications.ts +160 -0
  68. package/server/events/pub-sub/index.ts +45 -0
  69. package/server/events/relay-client.ts +288 -0
  70. package/server/events/scheduler-adapter.ts +302 -0
  71. package/server/events/session-store/index.ts +492 -0
  72. package/server/events/task-manager/index.ts +181 -0
  73. package/server/index.ts +572 -0
  74. package/server/system/config.ts +243 -0
  75. package/server/system/credentials.ts +220 -0
  76. package/server/system/docker.ts +97 -0
  77. package/server/system/env.ts +109 -0
  78. package/server/system/logger/config.ts +112 -0
  79. package/server/system/logger/formatters.ts +40 -0
  80. package/server/system/logger/index.ts +53 -0
  81. package/server/system/logger/rotation.ts +37 -0
  82. package/server/system/logger/sinks.ts +101 -0
  83. package/server/system/logger/types.ts +29 -0
  84. package/server/utils/date.ts +57 -0
  85. package/server/utils/errors.ts +7 -0
  86. package/server/utils/fetch.ts +27 -0
  87. package/server/utils/files/atomic.ts +125 -0
  88. package/server/utils/files/html-io.ts +20 -0
  89. package/server/utils/files/image-store.ts +66 -0
  90. package/server/utils/files/index.ts +45 -0
  91. package/server/utils/files/journal-io.ts +213 -0
  92. package/server/utils/files/json.ts +69 -0
  93. package/server/utils/files/markdown-store.ts +33 -0
  94. package/server/utils/files/naming.ts +50 -0
  95. package/server/utils/files/reference-dirs-io.ts +45 -0
  96. package/server/utils/files/roles-io.ts +45 -0
  97. package/server/utils/files/safe.ts +106 -0
  98. package/server/utils/files/scheduler-io.ts +20 -0
  99. package/server/utils/files/scheduler-overrides-io.ts +64 -0
  100. package/server/utils/files/session-io.ts +136 -0
  101. package/server/utils/files/spreadsheet-store.ts +63 -0
  102. package/server/utils/files/todos-io.ts +29 -0
  103. package/server/utils/files/user-tasks-io.ts +25 -0
  104. package/server/utils/files/workspace-io.ts +221 -0
  105. package/server/utils/gemini.ts +59 -0
  106. package/server/utils/gitignore.ts +69 -0
  107. package/server/utils/http.ts +15 -0
  108. package/server/utils/httpError.ts +61 -0
  109. package/server/utils/id.ts +16 -0
  110. package/server/utils/json.ts +83 -0
  111. package/server/utils/logBackgroundError.ts +22 -0
  112. package/server/utils/markdown.ts +82 -0
  113. package/server/utils/request.ts +29 -0
  114. package/server/utils/slug.ts +50 -0
  115. package/server/utils/spawn.ts +62 -0
  116. package/server/utils/time.ts +34 -0
  117. package/server/utils/types.ts +47 -0
  118. package/server/workspace/chat-index/index.ts +153 -0
  119. package/server/workspace/chat-index/indexer.ts +209 -0
  120. package/server/workspace/chat-index/paths.ts +34 -0
  121. package/server/workspace/chat-index/summarizer.ts +247 -0
  122. package/server/workspace/chat-index/types.ts +38 -0
  123. package/server/workspace/custom-dirs.ts +220 -0
  124. package/server/workspace/helps/business.md +104 -0
  125. package/server/workspace/helps/github.md +23 -0
  126. package/server/workspace/helps/index.md +60 -0
  127. package/server/workspace/helps/mulmoscript.md +249 -0
  128. package/server/workspace/helps/sandbox.md +90 -0
  129. package/server/workspace/helps/spreadsheet.md +43 -0
  130. package/server/workspace/helps/telegram.md +135 -0
  131. package/server/workspace/helps/wiki.md +131 -0
  132. package/server/workspace/journal/archivist.ts +386 -0
  133. package/server/workspace/journal/dailyPass.ts +743 -0
  134. package/server/workspace/journal/diff.ts +71 -0
  135. package/server/workspace/journal/index.ts +185 -0
  136. package/server/workspace/journal/indexFile.ts +136 -0
  137. package/server/workspace/journal/linkRewrite.ts +4 -0
  138. package/server/workspace/journal/memoryExtractor.ts +130 -0
  139. package/server/workspace/journal/optimizationPass.ts +160 -0
  140. package/server/workspace/journal/paths.ts +76 -0
  141. package/server/workspace/journal/state.ts +125 -0
  142. package/server/workspace/paths.ts +158 -0
  143. package/server/workspace/reference-dirs.ts +252 -0
  144. package/server/workspace/roles.ts +37 -0
  145. package/server/workspace/skills/discovery.ts +125 -0
  146. package/server/workspace/skills/index.ts +10 -0
  147. package/server/workspace/skills/parser.ts +144 -0
  148. package/server/workspace/skills/paths.ts +41 -0
  149. package/server/workspace/skills/scheduler.ts +149 -0
  150. package/server/workspace/skills/types.ts +30 -0
  151. package/server/workspace/skills/user-tasks.ts +257 -0
  152. package/server/workspace/skills/writer.ts +189 -0
  153. package/server/workspace/sources/arxivDiscovery.ts +182 -0
  154. package/server/workspace/sources/classifier.ts +268 -0
  155. package/server/workspace/sources/fetchers/arxiv.ts +170 -0
  156. package/server/workspace/sources/fetchers/github.ts +106 -0
  157. package/server/workspace/sources/fetchers/githubIssues.ts +208 -0
  158. package/server/workspace/sources/fetchers/githubReleases.ts +186 -0
  159. package/server/workspace/sources/fetchers/index.ts +71 -0
  160. package/server/workspace/sources/fetchers/registerAll.ts +15 -0
  161. package/server/workspace/sources/fetchers/rss.ts +141 -0
  162. package/server/workspace/sources/fetchers/rssParser.ts +295 -0
  163. package/server/workspace/sources/httpFetcher.ts +230 -0
  164. package/server/workspace/sources/interests.ts +120 -0
  165. package/server/workspace/sources/paths.ts +110 -0
  166. package/server/workspace/sources/pipeline/dedup.ts +60 -0
  167. package/server/workspace/sources/pipeline/fetch.ts +136 -0
  168. package/server/workspace/sources/pipeline/index.ts +249 -0
  169. package/server/workspace/sources/pipeline/notify.ts +72 -0
  170. package/server/workspace/sources/pipeline/plan.ts +66 -0
  171. package/server/workspace/sources/pipeline/summarize.ts +189 -0
  172. package/server/workspace/sources/pipeline/write.ts +185 -0
  173. package/server/workspace/sources/rateLimiter.ts +148 -0
  174. package/server/workspace/sources/registry.ts +326 -0
  175. package/server/workspace/sources/robots.ts +271 -0
  176. package/server/workspace/sources/sourceState.ts +135 -0
  177. package/server/workspace/sources/taxonomy.ts +74 -0
  178. package/server/workspace/sources/types.ts +144 -0
  179. package/server/workspace/sources/urls.ts +112 -0
  180. package/server/workspace/tool-trace/classify.ts +114 -0
  181. package/server/workspace/tool-trace/index.ts +250 -0
  182. package/server/workspace/tool-trace/writeSearch.ts +98 -0
  183. package/server/workspace/wiki-backlinks/index.ts +107 -0
  184. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +144 -0
  185. package/server/workspace/workspace.ts +66 -0
  186. package/src/App.vue +720 -0
  187. package/src/assets/mulmo_bw.png +0 -0
  188. package/src/components/CanvasViewToggle.vue +27 -0
  189. package/src/components/ChatAttachmentPreview.vue +45 -0
  190. package/src/components/ChatImagePreview.vue +17 -0
  191. package/src/components/ChatInput.vue +208 -0
  192. package/src/components/FileContentHeader.vue +49 -0
  193. package/src/components/FileContentRenderer.vue +162 -0
  194. package/src/components/FileTree.vue +115 -0
  195. package/src/components/FileTreePane.vue +85 -0
  196. package/src/components/FilesView.vue +206 -0
  197. package/src/components/LockStatusPopup.vue +111 -0
  198. package/src/components/NotificationBell.vue +131 -0
  199. package/src/components/NotificationToast.vue +72 -0
  200. package/src/components/PluginLauncher.vue +138 -0
  201. package/src/components/RightSidebar.vue +113 -0
  202. package/src/components/RoleSelector.vue +64 -0
  203. package/src/components/SessionHistoryPanel.vue +176 -0
  204. package/src/components/SessionTabBar.vue +81 -0
  205. package/src/components/SettingsMcpTab.vue +350 -0
  206. package/src/components/SettingsModal.vue +275 -0
  207. package/src/components/SettingsReferenceDirsTab.vue +173 -0
  208. package/src/components/SettingsWorkspaceDirsTab.vue +174 -0
  209. package/src/components/SidebarHeader.vue +69 -0
  210. package/src/components/StackView.vue +360 -0
  211. package/src/components/SuggestionsPanel.vue +65 -0
  212. package/src/components/TodoExplorer.vue +358 -0
  213. package/src/components/ToolResultsPanel.vue +77 -0
  214. package/src/components/todo/TodoAddDialog.vue +131 -0
  215. package/src/components/todo/TodoEditDialog.vue +47 -0
  216. package/src/components/todo/TodoEditPanel.vue +113 -0
  217. package/src/components/todo/TodoKanbanView.vue +249 -0
  218. package/src/components/todo/TodoListView.vue +79 -0
  219. package/src/components/todo/TodoTableView.vue +177 -0
  220. package/src/composables/useActiveSession.ts +40 -0
  221. package/src/composables/useAppApi.ts +45 -0
  222. package/src/composables/useCanvasViewMode.ts +121 -0
  223. package/src/composables/useChatScroll.ts +47 -0
  224. package/src/composables/useClickOutside.ts +26 -0
  225. package/src/composables/useClipboardCopy.ts +44 -0
  226. package/src/composables/useContentDisplay.ts +52 -0
  227. package/src/composables/useDebugBeat.ts +23 -0
  228. package/src/composables/useDynamicFavicon.ts +115 -0
  229. package/src/composables/useEventListeners.ts +42 -0
  230. package/src/composables/useExpandedDirs.ts +64 -0
  231. package/src/composables/useFaviconState.ts +30 -0
  232. package/src/composables/useFileSelection.ts +115 -0
  233. package/src/composables/useFileSortMode.ts +24 -0
  234. package/src/composables/useFileTree.ts +85 -0
  235. package/src/composables/useFreshPluginData.ts +89 -0
  236. package/src/composables/useHealth.ts +38 -0
  237. package/src/composables/useImeAwareEnter.ts +57 -0
  238. package/src/composables/useKeyNavigation.ts +60 -0
  239. package/src/composables/useMarkdownLinkHandler.ts +46 -0
  240. package/src/composables/useMarkdownMode.ts +17 -0
  241. package/src/composables/useMcpTools.ts +71 -0
  242. package/src/composables/useMergedSessions.ts +27 -0
  243. package/src/composables/useNotifications.ts +90 -0
  244. package/src/composables/usePdfDownload.ts +60 -0
  245. package/src/composables/usePendingCalls.ts +77 -0
  246. package/src/composables/usePubSub.ts +85 -0
  247. package/src/composables/useRightSidebar.ts +23 -0
  248. package/src/composables/useRoles.ts +34 -0
  249. package/src/composables/useSandboxStatus.ts +67 -0
  250. package/src/composables/useSelectedResult.ts +49 -0
  251. package/src/composables/useSessionDerived.ts +51 -0
  252. package/src/composables/useSessionHistory.ts +81 -0
  253. package/src/composables/useSessionSync.ts +57 -0
  254. package/src/composables/useViewLayout.ts +55 -0
  255. package/src/config/apiRoutes.ts +173 -0
  256. package/src/config/pubsubChannels.ts +45 -0
  257. package/src/config/roles.ts +335 -0
  258. package/src/config/schedulerActions.ts +25 -0
  259. package/src/config/toolNames.ts +71 -0
  260. package/src/config/workspacePaths.ts +24 -0
  261. package/src/index.css +107 -0
  262. package/src/main.ts +25 -0
  263. package/src/plugins/canvas/Preview.vue +13 -0
  264. package/src/plugins/canvas/View.vue +333 -0
  265. package/src/plugins/canvas/definition.ts +38 -0
  266. package/src/plugins/canvas/index.ts +36 -0
  267. package/src/plugins/chart/Preview.vue +49 -0
  268. package/src/plugins/chart/View.vue +143 -0
  269. package/src/plugins/chart/definition.ts +58 -0
  270. package/src/plugins/chart/index.ts +52 -0
  271. package/src/plugins/editImage/Preview.vue +13 -0
  272. package/src/plugins/editImage/View.vue +13 -0
  273. package/src/plugins/editImage/definition.ts +27 -0
  274. package/src/plugins/editImage/index.ts +36 -0
  275. package/src/plugins/generateImage/Preview.vue +13 -0
  276. package/src/plugins/generateImage/View.vue +33 -0
  277. package/src/plugins/generateImage/definition.ts +32 -0
  278. package/src/plugins/generateImage/index.ts +56 -0
  279. package/src/plugins/manageRoles/Preview.vue +49 -0
  280. package/src/plugins/manageRoles/View.vue +525 -0
  281. package/src/plugins/manageRoles/definition.ts +43 -0
  282. package/src/plugins/manageRoles/index.ts +47 -0
  283. package/src/plugins/manageSkills/Preview.vue +21 -0
  284. package/src/plugins/manageSkills/View.vue +321 -0
  285. package/src/plugins/manageSkills/definition.ts +49 -0
  286. package/src/plugins/manageSkills/index.ts +49 -0
  287. package/src/plugins/manageSource/Preview.vue +33 -0
  288. package/src/plugins/manageSource/View.vue +697 -0
  289. package/src/plugins/manageSource/definition.ts +63 -0
  290. package/src/plugins/manageSource/index.ts +66 -0
  291. package/src/plugins/markdown/Preview.vue +77 -0
  292. package/src/plugins/markdown/View.vue +476 -0
  293. package/src/plugins/markdown/definition.ts +50 -0
  294. package/src/plugins/markdown/index.ts +36 -0
  295. package/src/plugins/presentHtml/Preview.vue +25 -0
  296. package/src/plugins/presentHtml/View.vue +52 -0
  297. package/src/plugins/presentHtml/definition.ts +27 -0
  298. package/src/plugins/presentHtml/helpers.ts +72 -0
  299. package/src/plugins/presentHtml/index.ts +41 -0
  300. package/src/plugins/presentMulmoScript/Preview.vue +23 -0
  301. package/src/plugins/presentMulmoScript/View.vue +1166 -0
  302. package/src/plugins/presentMulmoScript/definition.ts +95 -0
  303. package/src/plugins/presentMulmoScript/helpers.ts +162 -0
  304. package/src/plugins/presentMulmoScript/index.ts +40 -0
  305. package/src/plugins/scheduler/Preview.vue +67 -0
  306. package/src/plugins/scheduler/TasksTab.vue +205 -0
  307. package/src/plugins/scheduler/View.vue +565 -0
  308. package/src/plugins/scheduler/definition.ts +57 -0
  309. package/src/plugins/scheduler/index.ts +45 -0
  310. package/src/plugins/scheduler/viewModes.ts +26 -0
  311. package/src/plugins/spreadsheet/Preview.vue +29 -0
  312. package/src/plugins/spreadsheet/View.vue +997 -0
  313. package/src/plugins/spreadsheet/cellHighlights.ts +79 -0
  314. package/src/plugins/spreadsheet/definition.ts +121 -0
  315. package/src/plugins/spreadsheet/engine/calculator.ts +459 -0
  316. package/src/plugins/spreadsheet/engine/cellBuilder.ts +81 -0
  317. package/src/plugins/spreadsheet/engine/date-parser.ts +220 -0
  318. package/src/plugins/spreadsheet/engine/date-utils.ts +56 -0
  319. package/src/plugins/spreadsheet/engine/engine.ts +176 -0
  320. package/src/plugins/spreadsheet/engine/evaluator.ts +390 -0
  321. package/src/plugins/spreadsheet/engine/formatter.ts +172 -0
  322. package/src/plugins/spreadsheet/engine/formulaRefs.ts +101 -0
  323. package/src/plugins/spreadsheet/engine/functions/date.ts +299 -0
  324. package/src/plugins/spreadsheet/engine/functions/financial.ts +387 -0
  325. package/src/plugins/spreadsheet/engine/functions/index.ts +16 -0
  326. package/src/plugins/spreadsheet/engine/functions/logical.ts +262 -0
  327. package/src/plugins/spreadsheet/engine/functions/lookup.ts +400 -0
  328. package/src/plugins/spreadsheet/engine/functions/mathematical.ts +297 -0
  329. package/src/plugins/spreadsheet/engine/functions/statistical.ts +338 -0
  330. package/src/plugins/spreadsheet/engine/functions/text.ts +389 -0
  331. package/src/plugins/spreadsheet/engine/index.ts +27 -0
  332. package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +111 -0
  333. package/src/plugins/spreadsheet/engine/parser.ts +143 -0
  334. package/src/plugins/spreadsheet/engine/registry.ts +150 -0
  335. package/src/plugins/spreadsheet/engine/responseDecoder.ts +67 -0
  336. package/src/plugins/spreadsheet/engine/types.ts +64 -0
  337. package/src/plugins/spreadsheet/index.ts +36 -0
  338. package/src/plugins/textResponse/Preview.vue +94 -0
  339. package/src/plugins/textResponse/View.vue +503 -0
  340. package/src/plugins/textResponse/definition.ts +34 -0
  341. package/src/plugins/textResponse/index.ts +27 -0
  342. package/src/plugins/textResponse/plugin.ts +29 -0
  343. package/src/plugins/textResponse/samples.ts +97 -0
  344. package/src/plugins/textResponse/types.ts +11 -0
  345. package/src/plugins/todo/Preview.vue +63 -0
  346. package/src/plugins/todo/View.vue +364 -0
  347. package/src/plugins/todo/composables/useTodos.ts +177 -0
  348. package/src/plugins/todo/definition.ts +45 -0
  349. package/src/plugins/todo/index.ts +61 -0
  350. package/src/plugins/todo/labels.ts +163 -0
  351. package/src/plugins/todo/priority.ts +98 -0
  352. package/src/plugins/todo/viewModes.ts +19 -0
  353. package/src/plugins/ui-image/ImagePreview.vue +23 -0
  354. package/src/plugins/ui-image/ImageView.vue +34 -0
  355. package/src/plugins/ui-image/index.ts +3 -0
  356. package/src/plugins/ui-image/types.ts +4 -0
  357. package/src/plugins/wiki/Preview.vue +65 -0
  358. package/src/plugins/wiki/View.vue +342 -0
  359. package/src/plugins/wiki/definition.ts +25 -0
  360. package/src/plugins/wiki/helpers.ts +59 -0
  361. package/src/plugins/wiki/index.ts +52 -0
  362. package/src/router/guards.ts +61 -0
  363. package/src/router/index.ts +50 -0
  364. package/src/tools/index.ts +52 -0
  365. package/src/tools/types.ts +27 -0
  366. package/src/types/events.ts +16 -0
  367. package/src/types/fileTree.ts +13 -0
  368. package/src/types/notification.ts +67 -0
  369. package/src/types/session.ts +116 -0
  370. package/src/types/sse.ts +90 -0
  371. package/src/types/toolCallHistory.ts +13 -0
  372. package/src/utils/agent/eventDispatch.ts +74 -0
  373. package/src/utils/agent/request.ts +55 -0
  374. package/src/utils/agent/toolCalls.ts +62 -0
  375. package/src/utils/api.ts +218 -0
  376. package/src/utils/canvas/viewMode.ts +46 -0
  377. package/src/utils/dom/authTokenMeta.ts +20 -0
  378. package/src/utils/dom/clickOutside.ts +11 -0
  379. package/src/utils/dom/externalLink.ts +57 -0
  380. package/src/utils/dom/scrollable.ts +24 -0
  381. package/src/utils/errors.ts +11 -0
  382. package/src/utils/files/expandedDirs.ts +25 -0
  383. package/src/utils/files/filename.ts +12 -0
  384. package/src/utils/files/sortChildren.ts +20 -0
  385. package/src/utils/filesPreview/schedulerPreview.ts +38 -0
  386. package/src/utils/filesPreview/todoPreview.ts +40 -0
  387. package/src/utils/format/date.ts +85 -0
  388. package/src/utils/format/frontmatter.ts +80 -0
  389. package/src/utils/format/jsonSyntax.ts +109 -0
  390. package/src/utils/html/previewCsp.ts +65 -0
  391. package/src/utils/image/resolve.ts +8 -0
  392. package/src/utils/image/rewriteMarkdownImageRefs.ts +182 -0
  393. package/src/utils/markdown/extractFirstH1.ts +39 -0
  394. package/src/utils/notification/dispatch.ts +22 -0
  395. package/src/utils/path/relativeLink.ts +130 -0
  396. package/src/utils/role/icon.ts +20 -0
  397. package/src/utils/role/merge.ts +10 -0
  398. package/src/utils/role/plugins.ts +12 -0
  399. package/src/utils/session/mergeSessions.ts +103 -0
  400. package/src/utils/session/seedRoleDefault.ts +35 -0
  401. package/src/utils/session/sessionEntries.ts +121 -0
  402. package/src/utils/session/sessionFactory.ts +22 -0
  403. package/src/utils/session/sessionHelpers.ts +99 -0
  404. package/src/utils/tools/dedup.ts +17 -0
  405. package/src/utils/tools/mcp.ts +33 -0
  406. package/src/utils/tools/pendingCalls.ts +16 -0
  407. package/src/utils/tools/result.ts +40 -0
  408. package/src/utils/types.ts +44 -0
@@ -0,0 +1,540 @@
1
+ // HTTP routes for the source registry.
2
+ //
3
+ // GET /api/sources — list all registered sources
4
+ // POST /api/sources — register a new source; auto-classify
5
+ // DELETE /api/sources/:slug — remove a source + its runtime state
6
+ // POST /api/sources/rebuild — run the daily pipeline manually
7
+ //
8
+ // Follow-up commits will add per-source `fetch` and `recategorize`
9
+ // actions. Everything here goes through the same
10
+ // validation-at-boundary discipline as the other routes so a
11
+ // malformed body never reaches the registry files.
12
+
13
+ import { createHash } from "node:crypto";
14
+ import { Router, Request, Response } from "express";
15
+ import { workspacePath } from "../../workspace/workspace.js";
16
+ import { log } from "../../system/logger/index.js";
17
+ import { deleteSource, listSources, readSource, writeSource } from "../../workspace/sources/registry.js";
18
+ import { deleteSourceState } from "../../workspace/sources/sourceState.js";
19
+ import { classifySource } from "../../workspace/sources/classifier.js";
20
+ import { runSourcesPipeline } from "../../workspace/sources/pipeline/index.js";
21
+ import { defaultHttpFetcherDeps, type RobotsProvider } from "../../workspace/sources/httpFetcher.js";
22
+ import { isValidSlug, slugify } from "../../utils/slug.js";
23
+ import {
24
+ FETCHER_KINDS,
25
+ SOURCE_SCHEDULES,
26
+ defaultSourceState,
27
+ isFetcherKind,
28
+ isSourceSchedule,
29
+ type Source,
30
+ type SourceSchedule,
31
+ type FetcherKind,
32
+ type FetcherParams,
33
+ } from "../../workspace/sources/types.js";
34
+ import { normalizeCategories, type CategorySlug } from "../../workspace/sources/taxonomy.js";
35
+ import { badRequest, conflict, sendError, serverError } from "../../utils/httpError.js";
36
+ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
37
+ import { isNonEmptyString, isRecord } from "../../utils/types.js";
38
+
39
+ const router = Router();
40
+
41
+ // Temporary no-op robots provider — the real implementation that
42
+ // reads `workspace/sources/_state/robots/<host>.txt` with 24h TTL
43
+ // lands in a follow-up PR. Phase-1 safe because the manageSource
44
+ // route only hits well-known public endpoints (GitHub / arXiv /
45
+ // RSS feeds the user explicitly registers).
46
+ const NO_ROBOTS: RobotsProvider = async () => null;
47
+
48
+ // --- GET /api/sources ---------------------------------------------------
49
+
50
+ interface ListSourcesResponse {
51
+ sources: Source[];
52
+ }
53
+ interface ErrorResponse {
54
+ error: string;
55
+ }
56
+
57
+ router.get(API_ROUTES.sources.list, async (_req: Request, res: Response<ListSourcesResponse | ErrorResponse>) => {
58
+ try {
59
+ const sources = await listSources(workspacePath);
60
+ res.json({ sources });
61
+ } catch (err) {
62
+ log.warn("sources", "list failed", { error: String(err) });
63
+ serverError(res, err instanceof Error ? err.message : "unknown error");
64
+ }
65
+ });
66
+
67
+ // --- POST /api/sources --------------------------------------------------
68
+
69
+ interface RegisterSourceBody {
70
+ title?: unknown;
71
+ url?: unknown;
72
+ fetcherKind?: unknown;
73
+ fetcherParams?: unknown;
74
+ schedule?: unknown;
75
+ slug?: unknown;
76
+ categories?: unknown;
77
+ notes?: unknown;
78
+ maxItemsPerFetch?: unknown;
79
+ // Pass `true` to skip the auto-classifier call (useful when the
80
+ // client already knows which categories they want, or for
81
+ // testing without the `claude` CLI installed).
82
+ skipClassify?: unknown;
83
+ }
84
+
85
+ interface RegisterSourceResponse {
86
+ source: Source;
87
+ classifyRationale?: string;
88
+ }
89
+
90
+ router.post(API_ROUTES.sources.create, async (req: Request<object, unknown, RegisterSourceBody>, res: Response<RegisterSourceResponse | ErrorResponse>) => {
91
+ const parsed = parseRegisterBody(req.body ?? {});
92
+ if ("error" in parsed) {
93
+ sendError(res, parsed.status, parsed.error);
94
+ return;
95
+ }
96
+ const existing = await readSource(workspacePath, parsed.slug);
97
+ if (existing) {
98
+ conflict(res, `source "${parsed.slug}" already exists`);
99
+ return;
100
+ }
101
+ const { categories, rationale } = await resolveCategories(parsed);
102
+ const source: Source = {
103
+ slug: parsed.slug,
104
+ title: parsed.title,
105
+ url: parsed.url,
106
+ fetcherKind: parsed.fetcherKind,
107
+ fetcherParams: parsed.fetcherParams,
108
+ schedule: parsed.schedule,
109
+ categories,
110
+ maxItemsPerFetch: parsed.maxItemsPerFetch,
111
+ addedAt: new Date().toISOString(),
112
+ notes: parsed.notes,
113
+ };
114
+ try {
115
+ await writeSource(workspacePath, source);
116
+ } catch (err) {
117
+ serverError(res, err instanceof Error ? err.message : "failed to write source");
118
+ return;
119
+ }
120
+ log.info("sources", "source registered", {
121
+ slug: parsed.slug,
122
+ fetcherKind: parsed.fetcherKind,
123
+ });
124
+ res.status(201).json({
125
+ source,
126
+ ...(rationale !== undefined && { classifyRationale: rationale }),
127
+ });
128
+ });
129
+
130
+ // --- DELETE /api/sources/:slug ------------------------------------------
131
+
132
+ interface DeleteSourceParams {
133
+ slug: string;
134
+ }
135
+
136
+ interface DeleteSourceResponse {
137
+ removed: boolean;
138
+ stateRemoved: boolean;
139
+ }
140
+
141
+ router.delete(API_ROUTES.sources.remove, async (req: Request<DeleteSourceParams>, res: Response<DeleteSourceResponse | ErrorResponse>) => {
142
+ const { slug } = req.params;
143
+ if (!isValidSlug(slug)) {
144
+ badRequest(res, "invalid slug");
145
+ return;
146
+ }
147
+ const removed = await deleteSource(workspacePath, slug);
148
+ const stateRemoved = await deleteSourceState(workspacePath, slug);
149
+ if (removed) {
150
+ log.info("sources", "source deleted", { slug });
151
+ }
152
+ res.json({ removed, stateRemoved });
153
+ });
154
+
155
+ // --- POST /api/sources/rebuild ------------------------------------------
156
+
157
+ interface RebuildBody {
158
+ scheduleType?: unknown;
159
+ }
160
+
161
+ router.post(API_ROUTES.sources.rebuild, async (req: Request<object, unknown, RebuildBody>, res: Response<ErrorResponse | Record<string, unknown>>) => {
162
+ const scheduleType = validateSchedule(req.body?.scheduleType, "daily");
163
+ if (!scheduleType) {
164
+ badRequest(res, `scheduleType must be one of: ${[...SOURCE_SCHEDULES].join(", ")}`);
165
+ return;
166
+ }
167
+ try {
168
+ log.info("sources", "manual rebuild triggered", { scheduleType });
169
+ const result = await runSourcesPipeline({
170
+ workspaceRoot: workspacePath,
171
+ scheduleType,
172
+ fetcherDeps: {
173
+ http: defaultHttpFetcherDeps(NO_ROBOTS),
174
+ now: () => Date.now(),
175
+ },
176
+ nowMs: () => Date.now(),
177
+ });
178
+ log.info("sources", "rebuild complete", {
179
+ plannedCount: result.plannedCount,
180
+ itemCount: result.items.length,
181
+ duplicateCount: result.dedup.duplicateCount,
182
+ archiveErrors: result.archiveErrors.length,
183
+ });
184
+ res.json({
185
+ plannedCount: result.plannedCount,
186
+ itemCount: result.items.length,
187
+ duplicateCount: result.dedup.duplicateCount,
188
+ dailyPath: result.dailyPath,
189
+ archiveWrittenPaths: result.archiveWrittenPaths,
190
+ archiveErrors: result.archiveErrors,
191
+ isoDate: result.isoDate,
192
+ });
193
+ } catch (err) {
194
+ log.warn("sources", "rebuild failed", { error: String(err) });
195
+ serverError(res, err instanceof Error ? err.message : "rebuild failed");
196
+ }
197
+ });
198
+
199
+ // --- POST /api/sources/manage -------------------------------------------
200
+ //
201
+ // MCP-friendly single-endpoint wrapper for the manageSource plugin.
202
+ // Every action returns { data: { sources, ... } } so the canvas
203
+ // View can re-render without a separate list call. Unlike the
204
+ // REST surface above, this endpoint never throws on validation —
205
+ // errors come back as 400/500 with { error } and the LLM gets a
206
+ // human-readable error to relay.
207
+
208
+ interface ManageSourceBody {
209
+ action?: unknown;
210
+ slug?: unknown;
211
+ title?: unknown;
212
+ url?: unknown;
213
+ fetcherKind?: unknown;
214
+ fetcherParams?: unknown;
215
+ schedule?: unknown;
216
+ categories?: unknown;
217
+ notes?: unknown;
218
+ }
219
+
220
+ interface ManageSourceData {
221
+ sources: Source[];
222
+ highlightSlug?: string;
223
+ classifyRationale?: string;
224
+ lastRebuild?: {
225
+ plannedCount: number;
226
+ itemCount: number;
227
+ duplicateCount: number;
228
+ archiveErrors: string[];
229
+ isoDate: string;
230
+ };
231
+ }
232
+
233
+ interface ManageSourceSuccess {
234
+ message: string;
235
+ instructions: string;
236
+ data: ManageSourceData;
237
+ }
238
+
239
+ const MANAGE_ACTIONS = new Set(["list", "register", "remove", "rebuild"]);
240
+
241
+ router.post(API_ROUTES.sources.manage, async (req: Request<object, unknown, ManageSourceBody>, res: Response<ManageSourceSuccess | ErrorResponse>) => {
242
+ const action = req.body?.action;
243
+ if (typeof action !== "string" || !MANAGE_ACTIONS.has(action)) {
244
+ badRequest(res, `action must be one of: ${[...MANAGE_ACTIONS].join(", ")}`);
245
+ return;
246
+ }
247
+ try {
248
+ switch (action) {
249
+ case "list":
250
+ await respondWithList(res, "Loaded source registry.");
251
+ return;
252
+ case "register":
253
+ await handleRegister(req.body, res);
254
+ return;
255
+ case "remove":
256
+ await handleRemove(req.body, res);
257
+ return;
258
+ case "rebuild":
259
+ await handleRebuild(res);
260
+ return;
261
+ }
262
+ } catch (err) {
263
+ log.warn("sources", "manage failed", { action, error: String(err) });
264
+ serverError(res, err instanceof Error ? err.message : "manage failed");
265
+ }
266
+ });
267
+
268
+ async function respondWithList(res: Response<ManageSourceSuccess | ErrorResponse>, message: string, extra: Partial<ManageSourceData> = {}): Promise<void> {
269
+ const sources = await listSources(workspacePath);
270
+ res.json({
271
+ message,
272
+ instructions: "The current information-source registry is now displayed in the canvas.",
273
+ data: { sources, ...extra },
274
+ });
275
+ }
276
+
277
+ async function handleRegister(body: ManageSourceBody, res: Response<ManageSourceSuccess | ErrorResponse>): Promise<void> {
278
+ const parsed = parseRegisterBody(body as RegisterSourceBody);
279
+ if ("error" in parsed) {
280
+ sendError(res, parsed.status, parsed.error);
281
+ return;
282
+ }
283
+ const existing = await readSource(workspacePath, parsed.slug);
284
+ if (existing) {
285
+ conflict(res, `source "${parsed.slug}" already exists`);
286
+ return;
287
+ }
288
+ const { categories, rationale } = await resolveCategories(parsed);
289
+ const source: Source = {
290
+ slug: parsed.slug,
291
+ title: parsed.title,
292
+ url: parsed.url,
293
+ fetcherKind: parsed.fetcherKind,
294
+ fetcherParams: parsed.fetcherParams,
295
+ schedule: parsed.schedule,
296
+ categories,
297
+ maxItemsPerFetch: parsed.maxItemsPerFetch,
298
+ addedAt: new Date().toISOString(),
299
+ notes: parsed.notes,
300
+ };
301
+ await writeSource(workspacePath, source);
302
+ log.info("sources", "source registered (manage)", {
303
+ slug: parsed.slug,
304
+ fetcherKind: parsed.fetcherKind,
305
+ });
306
+ await respondWithList(res, `Registered source "${parsed.slug}".`, {
307
+ highlightSlug: parsed.slug,
308
+ ...(rationale !== undefined && { classifyRationale: rationale }),
309
+ });
310
+ }
311
+
312
+ async function handleRemove(body: ManageSourceBody, res: Response<ManageSourceSuccess | ErrorResponse>): Promise<void> {
313
+ const slug = typeof body.slug === "string" ? body.slug.trim() : "";
314
+ if (!isValidSlug(slug)) {
315
+ res.status(400).json({ error: "slug is required and must be a valid slug" });
316
+ return;
317
+ }
318
+ const removed = await deleteSource(workspacePath, slug);
319
+ await deleteSourceState(workspacePath, slug);
320
+ if (removed) {
321
+ log.info("sources", "source deleted (manage)", { slug });
322
+ }
323
+ await respondWithList(res, removed ? `Removed source "${slug}".` : `No source "${slug}" was registered (nothing to remove).`);
324
+ }
325
+
326
+ async function handleRebuild(res: Response<ManageSourceSuccess | ErrorResponse>): Promise<void> {
327
+ log.info("sources", "manual rebuild triggered (manage)");
328
+ const result = await runSourcesPipeline({
329
+ workspaceRoot: workspacePath,
330
+ scheduleType: "daily",
331
+ fetcherDeps: {
332
+ http: defaultHttpFetcherDeps(NO_ROBOTS),
333
+ now: () => Date.now(),
334
+ },
335
+ nowMs: () => Date.now(),
336
+ });
337
+ log.info("sources", "rebuild complete (manage)", {
338
+ plannedCount: result.plannedCount,
339
+ itemCount: result.items.length,
340
+ duplicateCount: result.dedup.duplicateCount,
341
+ archiveErrors: result.archiveErrors.length,
342
+ });
343
+ await respondWithList(res, `Rebuild complete: ${result.items.length} items from ${result.plannedCount} sources.`, {
344
+ lastRebuild: {
345
+ plannedCount: result.plannedCount,
346
+ itemCount: result.items.length,
347
+ duplicateCount: result.dedup.duplicateCount,
348
+ archiveErrors: result.archiveErrors,
349
+ isoDate: result.isoDate,
350
+ },
351
+ });
352
+ }
353
+
354
+ // --- helpers ------------------------------------------------------------
355
+
356
+ // Parse + validate a fetcherKind body field. Returns null when
357
+ // invalid; defaults when absent. Exported for tests so a future
358
+ // stricter validator lands under this name.
359
+ export function validateFetcherKind(raw: unknown, defaultKind: FetcherKind): FetcherKind | null {
360
+ if (raw === undefined) return defaultKind;
361
+ if (isFetcherKind(raw)) return raw;
362
+ return null;
363
+ }
364
+
365
+ export function validateSchedule(raw: unknown, defaultSchedule: SourceSchedule): SourceSchedule | null {
366
+ if (raw === undefined) return defaultSchedule;
367
+ if (isSourceSchedule(raw)) return raw;
368
+ return null;
369
+ }
370
+
371
+ // Validate `fetcherParams` body. Expects a flat object of
372
+ // string values. Returns null when the shape is wrong (caller
373
+ // surfaces as 400). Returns empty object when the field is
374
+ // missing entirely.
375
+ export function validateFetcherParams(raw: unknown): FetcherParams | null {
376
+ if (raw === undefined) return {};
377
+ if (!isRecord(raw)) {
378
+ return null;
379
+ }
380
+ const out: FetcherParams = {};
381
+ for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
382
+ if (typeof value !== "string") return null;
383
+ out[key] = value;
384
+ }
385
+ return out;
386
+ }
387
+
388
+ // Resolve the slug for a new source. Prefers a caller-supplied
389
+ // slug when valid; otherwise derives from the title. Deterministic
390
+ // for the same title so re-registering without an explicit slug
391
+ // produces a predictable filename.
392
+ //
393
+ // If the title contains no ASCII letters / digits at all (e.g.
394
+ // pure CJK), fall back to a hash-based fallback — keeps non-
395
+ // English sources registrable even when they can't produce an
396
+ // ASCII-meaningful slug.
397
+ export function resolveSlug(rawSlug: unknown, title: string): string | null {
398
+ if (isNonEmptyString(rawSlug)) {
399
+ const candidate = rawSlug.trim();
400
+ return isValidSlug(candidate) ? candidate : null;
401
+ }
402
+ return deriveSourceSlug(title);
403
+ }
404
+
405
+ export function deriveSourceSlug(title: string): string {
406
+ const ascii = slugify(title, "", 60);
407
+ if (ascii.length > 0 && isValidSlug(ascii)) return ascii;
408
+ // Fallback: sha256 prefix. Base-16 so we only emit [0-9a-f]
409
+ // — matches the isValidSlug charset without needing base64url
410
+ // lowercase+underscore munging.
411
+ const hash = createHash("sha256")
412
+ .update(title.trim() || "untitled", "utf-8")
413
+ .digest("hex")
414
+ .slice(0, 10);
415
+ return `source-${hash}`;
416
+ }
417
+
418
+ // Parse + validate the POST /api/sources body into a fully-
419
+ // typed shape, or return an error envelope the caller can
420
+ // respond with directly. Extracted from the route handler so
421
+ // the validation logic fits under the cognitive-complexity cap.
422
+ interface ParsedRegisterBody {
423
+ title: string;
424
+ url: string;
425
+ fetcherKind: FetcherKind;
426
+ fetcherParams: FetcherParams;
427
+ schedule: SourceSchedule;
428
+ slug: string;
429
+ notes: string;
430
+ maxItemsPerFetch: number;
431
+ categoryOverride: CategorySlug[];
432
+ skipClassify: boolean;
433
+ }
434
+
435
+ type ParseError = { status: number; error: string };
436
+
437
+ function parseRegisterBody(body: RegisterSourceBody): ParsedRegisterBody | ParseError {
438
+ const title = typeof body.title === "string" ? body.title.trim() : "";
439
+ const url = typeof body.url === "string" ? body.url.trim() : "";
440
+ if (!title) return { status: 400, error: "title is required" };
441
+ if (!url) return { status: 400, error: "url is required" };
442
+ if (!isHttpUrl(url)) {
443
+ return {
444
+ status: 400,
445
+ error: "url must be an http:// or https:// URL",
446
+ };
447
+ }
448
+ const fetcherKind = validateFetcherKind(body.fetcherKind, "rss");
449
+ if (!fetcherKind) {
450
+ return {
451
+ status: 400,
452
+ error: `fetcherKind must be one of: ${[...FETCHER_KINDS].join(", ")}`,
453
+ };
454
+ }
455
+ const schedule = validateSchedule(body.schedule, "daily");
456
+ if (!schedule) {
457
+ return {
458
+ status: 400,
459
+ error: `schedule must be one of: ${[...SOURCE_SCHEDULES].join(", ")}`,
460
+ };
461
+ }
462
+ const fetcherParams = validateFetcherParams(body.fetcherParams);
463
+ if (fetcherParams === null) {
464
+ return {
465
+ status: 400,
466
+ error: "fetcherParams must be a flat object of string values",
467
+ };
468
+ }
469
+ const slug = resolveSlug(body.slug, title);
470
+ if (!slug) {
471
+ return {
472
+ status: 400,
473
+ error: "slug must match [a-z0-9](-[a-z0-9])* (or omit to auto-derive from title)",
474
+ };
475
+ }
476
+ return {
477
+ title,
478
+ url,
479
+ fetcherKind,
480
+ fetcherParams,
481
+ schedule,
482
+ slug,
483
+ notes: typeof body.notes === "string" ? body.notes : "",
484
+ maxItemsPerFetch: resolveMaxItemsPerFetch(body.maxItemsPerFetch),
485
+ categoryOverride: normalizeCategories(body.categories),
486
+ skipClassify: body.skipClassify === true,
487
+ };
488
+ }
489
+
490
+ function resolveMaxItemsPerFetch(raw: unknown): number {
491
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) {
492
+ return Math.floor(raw);
493
+ }
494
+ return 30;
495
+ }
496
+
497
+ // Decide the new source's categories: caller-provided override
498
+ // wins; otherwise run the classifier (unless explicitly skipped).
499
+ // Classifier failures are non-fatal — register the source with no
500
+ // categories and log the error so the user can `recategorize`
501
+ // later.
502
+ async function resolveCategories(parsed: ParsedRegisterBody): Promise<{ categories: CategorySlug[]; rationale?: string }> {
503
+ if (parsed.categoryOverride.length > 0) {
504
+ return { categories: parsed.categoryOverride };
505
+ }
506
+ if (parsed.skipClassify) return { categories: [] };
507
+ try {
508
+ const classifyResult = await classifySource({
509
+ title: parsed.title,
510
+ url: parsed.url,
511
+ notes: parsed.notes || undefined,
512
+ });
513
+ return {
514
+ categories: classifyResult.categories,
515
+ rationale: classifyResult.rationale,
516
+ };
517
+ } catch (err) {
518
+ log.warn("sources", "classify failed, saving with no categories", {
519
+ slug: parsed.slug,
520
+ error: String(err),
521
+ });
522
+ return { categories: [] };
523
+ }
524
+ }
525
+
526
+ function isHttpUrl(raw: string): boolean {
527
+ try {
528
+ const u = new URL(raw);
529
+ return u.protocol === "http:" || u.protocol === "https:";
530
+ } catch {
531
+ return false;
532
+ }
533
+ }
534
+
535
+ // Defensive unused-export to keep the route cohesive: the
536
+ // pipeline doesn't use this but a future per-source "fetch now"
537
+ // handler will.
538
+ export { defaultSourceState };
539
+
540
+ export default router;