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,125 @@
1
+ import { Router, Request, Response } from "express";
2
+ import { getSessionQuery } from "../../utils/request.js";
3
+ import { loadCustomRoles } from "../../workspace/roles.js";
4
+ import { BUILTIN_ROLES, type Role } from "../../../src/config/roles.js";
5
+ import { pushSessionEvent } from "../../events/session-store/index.js";
6
+ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
7
+ import { EVENT_TYPES } from "../../../src/types/events.js";
8
+ import { roleExists, deleteRole, saveRole } from "../../utils/files/roles-io.js";
9
+
10
+ const BUILTIN_IDS = new Set(BUILTIN_ROLES.map((r) => r.id));
11
+
12
+ const router = Router();
13
+
14
+ router.get(API_ROUTES.roles.list, (_req: Request, res: Response<Role[]>) => {
15
+ res.json(loadCustomRoles());
16
+ });
17
+
18
+ router.post(API_ROUTES.roles.manage, async (req: Request, res: Response<Record<string, unknown>>) => {
19
+ const session = getSessionQuery(req);
20
+ const result = await executeManageRoles(req.body, session);
21
+ res.json(result);
22
+ });
23
+
24
+ export default router;
25
+
26
+ function notifyRolesUpdated(chatSessionId: string): void {
27
+ pushSessionEvent(chatSessionId, { type: EVENT_TYPES.rolesUpdated });
28
+ }
29
+
30
+ interface ManageRolesInput {
31
+ action: string;
32
+ role?: {
33
+ id: string;
34
+ name: string;
35
+ icon: string;
36
+ prompt: string;
37
+ availablePlugins: string[];
38
+ queries?: string[];
39
+ };
40
+ roleId?: string;
41
+ oldRoleId?: string;
42
+ }
43
+
44
+ function listRolesResult(): Record<string, unknown> {
45
+ const customRoles = loadCustomRoles();
46
+ return {
47
+ success: true,
48
+ message: `${customRoles.length} custom role${customRoles.length !== 1 ? "s" : ""}.`,
49
+ data: { customRoles },
50
+ };
51
+ }
52
+
53
+ function deleteRoleResult(roleId: string | undefined, sessionId: string): Record<string, unknown> {
54
+ if (!roleId) return { success: false, error: "roleId is required for delete action" };
55
+ if (BUILTIN_IDS.has(roleId)) {
56
+ return { success: false, error: "Cannot delete built-in roles." };
57
+ }
58
+ if (!roleExists(roleId)) {
59
+ return { success: false, error: `Role '${roleId}' not found.` };
60
+ }
61
+ deleteRole(roleId);
62
+ notifyRolesUpdated(sessionId);
63
+ return {
64
+ success: true,
65
+ message: `Role '${roleId}' deleted.`,
66
+ roles: loadCustomRoles(),
67
+ };
68
+ }
69
+
70
+ function validateSaveInput(input: ManageRolesInput): { role: NonNullable<ManageRolesInput["role"]>; isRename: boolean } | string {
71
+ const { action, role, oldRoleId } = input;
72
+ if (!role) return "role definition required for create/update";
73
+ if (!role.id) return "role.id is required";
74
+
75
+ // Rename is strictly an update-with-different-id. Gating on
76
+ // action === "update" means a malformed create payload that
77
+ // happens to include `oldRoleId` cannot silently delete an
78
+ // unrelated file via the rename cleanup below.
79
+ const isRename = Boolean(action === "update" && oldRoleId && oldRoleId !== role.id);
80
+ if (BUILTIN_IDS.has(role.id) && (action === "create" || isRename)) {
81
+ return `ID '${role.id}' is reserved for a built-in role.`;
82
+ }
83
+ if ((action === "create" || isRename) && roleExists(role.id)) {
84
+ return `A role with ID '${role.id}' already exists.`;
85
+ }
86
+ return { role, isRename };
87
+ }
88
+
89
+ function saveRoleResult(input: ManageRolesInput, sessionId: string): Record<string, unknown> {
90
+ const validated = validateSaveInput(input);
91
+ if (typeof validated === "string") {
92
+ return { success: false, error: validated };
93
+ }
94
+ const { role, isRename } = validated;
95
+ const { action, oldRoleId } = input;
96
+
97
+ // Strip switchRole before saving — it is injected at load time by server/roles.ts
98
+ const pluginsToSave = role.availablePlugins ?? [];
99
+ const roleToSave = {
100
+ ...role,
101
+ availablePlugins: pluginsToSave.filter((p) => p !== "switchRole"),
102
+ };
103
+
104
+ saveRole(role.id, roleToSave);
105
+ // On rename, remove the old file even if its id matches a built-in —
106
+ // a file at `config/roles/<builtin>.json` is a user-created override,
107
+ // not the built-in itself (which lives in BUILTIN_ROLES). Leaving it
108
+ // behind would shadow the built-in and couldn't be cleaned up via
109
+ // `delete`, which also rejects built-in ids.
110
+ if (isRename && oldRoleId && roleExists(oldRoleId)) {
111
+ deleteRole(oldRoleId);
112
+ }
113
+ notifyRolesUpdated(sessionId);
114
+ return {
115
+ success: true,
116
+ message: `Role '${role.name}' ${action}d successfully.`,
117
+ roles: loadCustomRoles(),
118
+ };
119
+ }
120
+
121
+ export async function executeManageRoles(input: ManageRolesInput, sessionId: string): Promise<Record<string, unknown>> {
122
+ if (input.action === "list") return listRolesResult();
123
+ if (input.action === "delete") return deleteRoleResult(input.roleId, sessionId);
124
+ return saveRoleResult(input, sessionId);
125
+ }
@@ -0,0 +1,153 @@
1
+ import { Router, Request, Response } from "express";
2
+ import { loadSchedulerItems, saveSchedulerItems } from "../../utils/files/scheduler-io.js";
3
+ import { dispatchScheduler, type SchedulerActionInput } from "./schedulerHandlers.js";
4
+ import { respondWithDispatchResult, type DispatchSuccessResponse, type DispatchErrorResponse } from "./dispatchResponse.js";
5
+ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
6
+ import { SESSION_ORIGINS } from "../../../src/types/session.js";
7
+ import { loadUserTasks, validateAndCreate, refreshUserTasks } from "../../workspace/skills/user-tasks.js";
8
+ import { saveUserTasks } from "../../utils/files/user-tasks-io.js";
9
+ import { startChat } from "./agent.js";
10
+ import { log } from "../../system/logger/index.js";
11
+ import { SCHEDULER_ACTIONS, TASK_ACTIONS } from "../../../src/config/schedulerActions.js";
12
+
13
+ const router = Router();
14
+
15
+ export interface ScheduledItem {
16
+ id: string;
17
+ title: string;
18
+ createdAt: number;
19
+ props: Record<string, string | number | boolean | null>;
20
+ }
21
+
22
+ function loadItems(): ScheduledItem[] {
23
+ return loadSchedulerItems<ScheduledItem[]>([]);
24
+ }
25
+
26
+ function saveItems(items: ScheduledItem[]): void {
27
+ saveSchedulerItems(items);
28
+ }
29
+
30
+ router.get(API_ROUTES.scheduler.base, (_req: Request, res: Response<{ data: { items: ScheduledItem[] } }>) => {
31
+ res.json({ data: { items: loadItems() } });
32
+ });
33
+
34
+ interface SchedulerBody extends SchedulerActionInput {
35
+ action: string;
36
+ // Task-related fields
37
+ name?: string;
38
+ prompt?: string;
39
+ schedule?: unknown;
40
+ roleId?: string;
41
+ }
42
+
43
+ router.post(
44
+ API_ROUTES.scheduler.base,
45
+ async (req: Request<object, unknown, SchedulerBody>, res: Response<DispatchSuccessResponse<ScheduledItem> | DispatchErrorResponse | unknown>) => {
46
+ const { action, ...input } = req.body;
47
+
48
+ // Route task actions to the user-task subsystem
49
+ if (TASK_ACTIONS.has(action)) {
50
+ await handleTaskAction(action, input, res);
51
+ return;
52
+ }
53
+
54
+ // Calendar item actions (existing behavior)
55
+ const items = loadItems();
56
+ const result = dispatchScheduler(action, items, input);
57
+ respondWithDispatchResult(res, result, {
58
+ shouldPersist: action !== SCHEDULER_ACTIONS.show,
59
+ instructions: "Display the updated scheduler to the user.",
60
+ persist: saveItems,
61
+ });
62
+ },
63
+ );
64
+
65
+ async function handleTaskAction(action: string, input: Record<string, unknown>, res: Response): Promise<void> {
66
+ try {
67
+ if (action === SCHEDULER_ACTIONS.listTasks) {
68
+ const tasks = loadUserTasks();
69
+ res.json({
70
+ uuid: crypto.randomUUID(),
71
+ message: `${tasks.length} scheduled task(s) found.`,
72
+ data: { tasks },
73
+ });
74
+ return;
75
+ }
76
+
77
+ if (action === SCHEDULER_ACTIONS.createTask) {
78
+ const result = validateAndCreate(input);
79
+ if (result.kind === "error") {
80
+ res.status(400).json({ error: result.error });
81
+ return;
82
+ }
83
+ const tasks = loadUserTasks();
84
+ tasks.push(result.task);
85
+ await saveUserTasks(tasks);
86
+ await refreshUserTasks();
87
+ res.json({
88
+ uuid: crypto.randomUUID(),
89
+ message: `Task "${result.task.name}" created and scheduled.`,
90
+ data: { task: result.task },
91
+ });
92
+ return;
93
+ }
94
+
95
+ if (action === SCHEDULER_ACTIONS.deleteTask) {
96
+ const id = typeof input.id === "string" ? input.id : "";
97
+ const tasks = loadUserTasks();
98
+ const idx = tasks.findIndex((t) => t.id === id);
99
+ if (idx === -1) {
100
+ res.status(404).json({ error: `task not found: ${id}` });
101
+ return;
102
+ }
103
+ const name = tasks[idx].name;
104
+ tasks.splice(idx, 1);
105
+ await saveUserTasks(tasks);
106
+ await refreshUserTasks();
107
+ res.json({
108
+ uuid: crypto.randomUUID(),
109
+ message: `Task "${name}" deleted.`,
110
+ data: { deleted: id },
111
+ });
112
+ return;
113
+ }
114
+
115
+ if (action === SCHEDULER_ACTIONS.runTask) {
116
+ const id = typeof input.id === "string" ? input.id : "";
117
+ const tasks = loadUserTasks();
118
+ const task = tasks.find((t) => t.id === id);
119
+ if (!task) {
120
+ res.status(404).json({ error: `task not found: ${id}` });
121
+ return;
122
+ }
123
+ const chatSessionId = crypto.randomUUID();
124
+ log.info("scheduler", "manual run via MCP", {
125
+ name: task.name,
126
+ chatSessionId,
127
+ });
128
+ startChat({
129
+ message: task.prompt,
130
+ roleId: task.roleId,
131
+ chatSessionId,
132
+ origin: SESSION_ORIGINS.scheduler,
133
+ }).catch((err) => {
134
+ log.error("scheduler", "manual run failed", {
135
+ error: String(err),
136
+ });
137
+ });
138
+ res.json({
139
+ uuid: crypto.randomUUID(),
140
+ message: `Task "${task.name}" triggered.`,
141
+ data: { triggered: id, chatSessionId },
142
+ });
143
+ return;
144
+ }
145
+
146
+ res.status(400).json({ error: `unknown task action: ${action}` });
147
+ } catch (err) {
148
+ log.error("scheduler", "task action failed", { error: String(err) });
149
+ res.status(500).json({ error: "Internal server error" });
150
+ }
151
+ }
152
+
153
+ export default router;
@@ -0,0 +1,151 @@
1
+ // Pure action handlers for the scheduler POST route. Each handler
2
+ // takes the current items + the relevant body fields and returns
3
+ // a discriminated result describing either an HTTP error or the
4
+ // next state. The route handler in scheduler.ts dispatches to one
5
+ // of these and translates the result into an HTTP response.
6
+ //
7
+ // Keeping the action logic pure (no I/O, no globals) makes it
8
+ // straightforward to unit-test every case in isolation, and brings
9
+ // the cognitive complexity of the route handler under the lint
10
+ // threshold.
11
+
12
+ import type { ScheduledItem } from "./scheduler.js";
13
+ import { makeId } from "../../utils/id.js";
14
+
15
+ export interface SchedulerActionInput {
16
+ title?: string;
17
+ id?: string;
18
+ props?: Record<string, string | number | boolean | null>;
19
+ items?: ScheduledItem[];
20
+ }
21
+
22
+ export type SchedulerActionResult =
23
+ | { kind: "error"; status: number; error: string }
24
+ | {
25
+ kind: "success";
26
+ items: ScheduledItem[];
27
+ message: string;
28
+ jsonData: Record<string, unknown>;
29
+ };
30
+
31
+ export function sortItems(items: ScheduledItem[]): ScheduledItem[] {
32
+ return [...items].sort((a, b) => {
33
+ const aDate = typeof a.props.date === "string" ? a.props.date : null;
34
+ const bDate = typeof b.props.date === "string" ? b.props.date : null;
35
+ const aTime = typeof a.props.time === "string" ? a.props.time : "00:00";
36
+ const bTime = typeof b.props.time === "string" ? b.props.time : "00:00";
37
+ const aKey = aDate ? `0_${aDate}_${aTime}` : `1_${a.createdAt}`;
38
+ const bKey = bDate ? `0_${bDate}_${bTime}` : `1_${b.createdAt}`;
39
+ return aKey < bKey ? -1 : aKey > bKey ? 1 : 0;
40
+ });
41
+ }
42
+
43
+ export function handleShow(items: ScheduledItem[]): SchedulerActionResult {
44
+ return {
45
+ kind: "success",
46
+ items,
47
+ message: `Showing ${items.length} scheduled item(s)`,
48
+ jsonData: {},
49
+ };
50
+ }
51
+
52
+ export function handleAdd(items: ScheduledItem[], input: SchedulerActionInput): SchedulerActionResult {
53
+ if (!input.title) {
54
+ return { kind: "error", status: 400, error: "title required" };
55
+ }
56
+ const item: ScheduledItem = {
57
+ id: makeId("sched"),
58
+ title: input.title,
59
+ createdAt: Date.now(),
60
+ props: input.props ?? {},
61
+ };
62
+ const next = sortItems([...items, item]);
63
+ return {
64
+ kind: "success",
65
+ items: next,
66
+ message: `Added: "${input.title}"`,
67
+ jsonData: { added: item.id },
68
+ };
69
+ }
70
+
71
+ export function handleDelete(items: ScheduledItem[], input: SchedulerActionInput): SchedulerActionResult {
72
+ if (!input.id) {
73
+ return { kind: "error", status: 400, error: "id required" };
74
+ }
75
+ const next = items.filter((i) => i.id !== input.id);
76
+ const found = next.length < items.length;
77
+ return {
78
+ kind: "success",
79
+ items: next,
80
+ message: found ? `Deleted item ${input.id}` : `Item not found: ${input.id}`,
81
+ jsonData: { deleted: input.id },
82
+ };
83
+ }
84
+
85
+ function applyPropPatch(current: ScheduledItem["props"], patch: Record<string, string | number | boolean | null>): ScheduledItem["props"] {
86
+ const next: ScheduledItem["props"] = { ...current };
87
+ for (const [k, v] of Object.entries(patch)) {
88
+ if (v === null) {
89
+ delete next[k];
90
+ } else {
91
+ next[k] = v;
92
+ }
93
+ }
94
+ return next;
95
+ }
96
+
97
+ export function handleUpdate(items: ScheduledItem[], input: SchedulerActionInput): SchedulerActionResult {
98
+ if (!input.id) {
99
+ return { kind: "error", status: 400, error: "id required" };
100
+ }
101
+ const target = items.find((i) => i.id === input.id);
102
+ if (!target) {
103
+ return {
104
+ kind: "success",
105
+ items,
106
+ message: `Item not found: ${input.id}`,
107
+ jsonData: {},
108
+ };
109
+ }
110
+ const updated: ScheduledItem = {
111
+ ...target,
112
+ title: input.title !== undefined ? input.title : target.title,
113
+ props: input.props !== undefined ? applyPropPatch(target.props, input.props) : target.props,
114
+ };
115
+ const next = sortItems(items.map((i) => (i.id === input.id ? updated : i)));
116
+ return {
117
+ kind: "success",
118
+ items: next,
119
+ message: `Updated: "${updated.title}"`,
120
+ jsonData: { updated: input.id },
121
+ };
122
+ }
123
+
124
+ export function handleReplace(_items: ScheduledItem[], input: SchedulerActionInput): SchedulerActionResult {
125
+ if (!Array.isArray(input.items)) {
126
+ return { kind: "error", status: 400, error: "items array required" };
127
+ }
128
+ const next = sortItems(input.items);
129
+ return {
130
+ kind: "success",
131
+ items: next,
132
+ message: `Replaced all items (${next.length} total)`,
133
+ jsonData: { count: next.length },
134
+ };
135
+ }
136
+
137
+ const HANDLERS: Record<string, (items: ScheduledItem[], input: SchedulerActionInput) => SchedulerActionResult> = {
138
+ show: handleShow,
139
+ add: handleAdd,
140
+ delete: handleDelete,
141
+ update: handleUpdate,
142
+ replace: handleReplace,
143
+ };
144
+
145
+ export function dispatchScheduler(action: string, items: ScheduledItem[], input: SchedulerActionInput): SchedulerActionResult {
146
+ const handler = HANDLERS[action];
147
+ if (!handler) {
148
+ return { kind: "error", status: 400, error: `Unknown action: ${action}` };
149
+ }
150
+ return handler(items, input);
151
+ }
@@ -0,0 +1,163 @@
1
+ // API routes for the unified scheduler (#357).
2
+ //
3
+ // GET /api/scheduler/tasks — all registered tasks + state
4
+ // POST /api/scheduler/tasks — create user task
5
+ // PUT /api/scheduler/tasks/:id — update user task
6
+ // DELETE /api/scheduler/tasks/:id — delete user task
7
+ // POST /api/scheduler/tasks/:id/run — manual trigger
8
+ // GET /api/scheduler/logs — execution log (newest first)
9
+
10
+ import { Router, type Request, type Response } from "express";
11
+ import { getSchedulerTasks, getSchedulerLogs } from "../../events/scheduler-adapter.js";
12
+ import type { TaskLogEntry } from "@receptron/task-scheduler";
13
+ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
14
+ import { SESSION_ORIGINS } from "../../../src/types/session.js";
15
+ import { loadUserTasks, validateAndCreate, applyUpdate, withUserTaskLock } from "../../workspace/skills/user-tasks.js";
16
+ import { badRequest, notFound, serverError } from "../../utils/httpError.js";
17
+ import { errorMessage } from "../../utils/errors.js";
18
+ import { log } from "../../system/logger/index.js";
19
+ import { startChat } from "./agent.js";
20
+
21
+ const router = Router();
22
+
23
+ // ── List all tasks ──────────────────────────────────────────────
24
+
25
+ router.get(API_ROUTES.scheduler.tasks, (_req: Request, res: Response) => {
26
+ // getSchedulerTasks() returns system-only tasks (registered via
27
+ // initScheduler at startup — journal, chat-index, sources, etc.).
28
+ // origin: "system" is correct, not an overwrite — these tasks
29
+ // have no origin field of their own.
30
+ const systemTasks = getSchedulerTasks();
31
+ const userTasks = loadUserTasks();
32
+ const all = [...systemTasks.map((t) => ({ ...t, origin: "system" as const })), ...userTasks.map((t) => ({ ...t, origin: "user" as const }))];
33
+ res.json({ tasks: all });
34
+ });
35
+
36
+ // ── Create user task ────────────────────────────────────────────
37
+
38
+ router.post(API_ROUTES.scheduler.tasks, async (req: Request, res: Response) => {
39
+ const validated = validateAndCreate(req.body);
40
+ if (validated.kind === "error") {
41
+ badRequest(res, validated.error);
42
+ return;
43
+ }
44
+ try {
45
+ const task = await withUserTaskLock(async (tasks) => ({
46
+ tasks: [...tasks, validated.task],
47
+ result: validated.task,
48
+ }));
49
+ res.status(201).json({ task });
50
+ } catch (err) {
51
+ log.error("scheduler-tasks", "create failed", {
52
+ error: String(err),
53
+ });
54
+ serverError(res, "Failed to create task");
55
+ }
56
+ });
57
+
58
+ // ── Update user task ────────────────────────────────────────────
59
+
60
+ router.put(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, res: Response) => {
61
+ const { id } = req.params;
62
+ try {
63
+ const updated = await withUserTaskLock(async (tasks) => {
64
+ const result = applyUpdate(tasks, id, req.body);
65
+ if (result.kind === "error") {
66
+ throw new Error(result.error);
67
+ }
68
+ const task = result.tasks.find((t) => t.id === id);
69
+ return { tasks: result.tasks, result: task };
70
+ });
71
+ res.json({ task: updated });
72
+ } catch (err) {
73
+ const msg = errorMessage(err);
74
+ if (msg.startsWith("task not found") || msg.startsWith("request body")) {
75
+ notFound(res, msg);
76
+ return;
77
+ }
78
+ log.error("scheduler-tasks", "update failed", { error: msg });
79
+ serverError(res, "Failed to update task");
80
+ }
81
+ });
82
+
83
+ // ── Delete user task ────────────────────────────────────────────
84
+
85
+ router.delete(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, res: Response) => {
86
+ const { id } = req.params;
87
+ try {
88
+ await withUserTaskLock(async (tasks) => {
89
+ const idx = tasks.findIndex((t) => t.id === id);
90
+ if (idx === -1) throw new Error(`task not found: ${id}`);
91
+ const next = tasks.filter((t) => t.id !== id);
92
+ return { tasks: next, result: undefined };
93
+ });
94
+ res.json({ deleted: id });
95
+ } catch (err) {
96
+ const msg = errorMessage(err);
97
+ if (msg.startsWith("task not found")) {
98
+ notFound(res, msg);
99
+ return;
100
+ }
101
+ log.error("scheduler-tasks", "delete failed", { error: msg });
102
+ serverError(res, "Failed to delete task");
103
+ }
104
+ });
105
+
106
+ // ── Manual trigger ──────────────────────────────────────────────
107
+
108
+ router.post(API_ROUTES.scheduler.taskRun, async (req: Request<{ id: string }>, res: Response) => {
109
+ const { id } = req.params;
110
+ // Check user tasks first
111
+ const userTasks = loadUserTasks();
112
+ const userTask = userTasks.find((t) => t.id === id);
113
+ if (userTask) {
114
+ const chatSessionId = crypto.randomUUID();
115
+ log.info("scheduler-tasks", "manual run (user task)", {
116
+ name: userTask.name,
117
+ chatSessionId,
118
+ });
119
+ startChat({
120
+ message: userTask.prompt,
121
+ roleId: userTask.roleId,
122
+ chatSessionId,
123
+ origin: SESSION_ORIGINS.scheduler,
124
+ }).catch((err) => {
125
+ log.error("scheduler-tasks", "manual run failed", {
126
+ error: String(err),
127
+ });
128
+ });
129
+ res.json({ triggered: id, chatSessionId });
130
+ return;
131
+ }
132
+ // Not a user task — check system/skill tasks
133
+ const systemTasks = getSchedulerTasks();
134
+ const found = systemTasks.find((t) => t.id === id);
135
+ if (!found) {
136
+ notFound(res, `task not found: ${id}`);
137
+ return;
138
+ }
139
+ // System tasks don't have a prompt to startChat with — return 400
140
+ badRequest(res, "manual run is only supported for user tasks");
141
+ });
142
+
143
+ // ── Execution logs ──────────────────────────────────────────────
144
+
145
+ interface LogQuery {
146
+ since?: string;
147
+ taskId?: string;
148
+ limit?: string;
149
+ }
150
+
151
+ router.get(API_ROUTES.scheduler.logs, async (req: Request<object, unknown, object, LogQuery>, res: Response<{ logs: TaskLogEntry[] }>) => {
152
+ const MAX_LIMIT = 500;
153
+ const rawLimit = typeof req.query.limit === "string" ? parseInt(req.query.limit, 10) : undefined;
154
+ const limit = Number.isFinite(rawLimit) && rawLimit! > 0 ? Math.min(rawLimit!, MAX_LIMIT) : undefined;
155
+ const logs = await getSchedulerLogs({
156
+ since: typeof req.query.since === "string" ? req.query.since : undefined,
157
+ taskId: typeof req.query.taskId === "string" ? req.query.taskId : undefined,
158
+ limit,
159
+ });
160
+ res.json({ logs });
161
+ });
162
+
163
+ export default router;