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,565 @@
1
+ <template>
2
+ <div class="h-full bg-white flex flex-col">
3
+ <!-- API error banner — surfaces POST /api/scheduler failures so a
4
+ delete/add/replace that silently no-ops becomes diagnosable. -->
5
+ <div v-if="apiError" class="px-4 py-2 bg-red-50 border-b border-red-200 text-sm text-red-700" role="alert" data-testid="scheduler-api-error">
6
+ ⚠ Failed to update scheduler: {{ apiError }}
7
+ </div>
8
+ <!-- Top-level tab bar: Calendar / Tasks -->
9
+ <div class="flex border-b border-gray-200 px-6">
10
+ <button
11
+ class="px-4 py-2 text-sm font-medium border-b-2 -mb-px"
12
+ :class="activeTab === SCHEDULER_TAB.calendar ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
13
+ data-testid="scheduler-tab-calendar"
14
+ @click="activeTab = SCHEDULER_TAB.calendar"
15
+ >
16
+ Calendar
17
+ </button>
18
+ <button
19
+ class="px-4 py-2 text-sm font-medium border-b-2 -mb-px"
20
+ :class="activeTab === SCHEDULER_TAB.tasks ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
21
+ data-testid="scheduler-tab-tasks"
22
+ @click="activeTab = SCHEDULER_TAB.tasks"
23
+ >
24
+ Tasks
25
+ </button>
26
+ </div>
27
+
28
+ <!-- Tasks tab -->
29
+ <TasksTab v-if="activeTab === SCHEDULER_TAB.tasks" />
30
+
31
+ <!-- Calendar tab (existing content) -->
32
+ <template v-if="activeTab === SCHEDULER_TAB.calendar">
33
+ <!-- Header -->
34
+ <div class="flex items-center justify-between px-6 py-3 border-b border-gray-100">
35
+ <div class="flex items-center gap-3">
36
+ <h2 class="text-lg font-semibold text-gray-800">Scheduler</h2>
37
+ <span class="text-sm text-gray-500">{{ items.length }} item{{ items.length !== 1 ? "s" : "" }}</span>
38
+ </div>
39
+ <div class="flex items-center gap-2">
40
+ <!-- Navigation (calendar modes only) -->
41
+ <template v-if="viewMode !== SCHEDULER_VIEW.list">
42
+ <button class="px-2 py-1 text-sm text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded" title="Previous" @click="goPrev">
43
+ <span class="material-icons text-sm">chevron_left</span>
44
+ </button>
45
+ <button class="px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded" title="Go to today" @click="goToday">Today</button>
46
+ <button class="px-2 py-1 text-sm text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded" title="Next" @click="goNext">
47
+ <span class="material-icons text-sm">chevron_right</span>
48
+ </button>
49
+ <span class="text-sm text-gray-600 min-w-[140px] text-center">{{ headerLabel }}</span>
50
+ </template>
51
+ <!-- View mode toggle -->
52
+ <div class="flex border border-gray-300 rounded overflow-hidden text-xs">
53
+ <button
54
+ v-for="mode in VIEW_MODES"
55
+ :key="mode.key"
56
+ class="px-2.5 py-1"
57
+ :class="viewMode === mode.key ? 'bg-blue-500 text-white' : 'bg-white text-gray-600 hover:bg-gray-50'"
58
+ :title="mode.label"
59
+ @click="viewMode = mode.key"
60
+ >
61
+ <span class="material-icons text-sm">{{ mode.icon }}</span>
62
+ </button>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- List view -->
68
+ <div v-if="viewMode === SCHEDULER_VIEW.list" class="flex-1 overflow-y-auto min-h-0">
69
+ <div v-if="items.length === 0" class="flex items-center justify-center h-full text-gray-400">No scheduled items</div>
70
+
71
+ <ul v-else class="p-4 space-y-2">
72
+ <li
73
+ v-for="item in items"
74
+ :key="item.id"
75
+ class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer group"
76
+ :class="selectedId === item.id ? 'border-blue-400 bg-blue-50' : 'border-gray-200 hover:bg-gray-50'"
77
+ @click="selectItem(item)"
78
+ >
79
+ <div class="flex-1 min-w-0">
80
+ <div class="font-medium text-gray-800 text-sm">
81
+ {{ item.title }}
82
+ </div>
83
+ <div v-if="Object.keys(item.props).length > 0" class="flex flex-wrap gap-1 mt-1">
84
+ <span
85
+ v-for="(val, key) in item.props"
86
+ :key="key"
87
+ class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-gray-100 text-xs text-gray-600"
88
+ >
89
+ <span class="text-gray-400">{{ key }}:</span>
90
+ <span>{{ val }}</span>
91
+ </span>
92
+ </div>
93
+ </div>
94
+ <button
95
+ class="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 text-xs px-1 mt-0.5 shrink-0"
96
+ title="Delete item"
97
+ @click.stop="remove(item)"
98
+ >
99
+
100
+ </button>
101
+ </li>
102
+ </ul>
103
+ </div>
104
+
105
+ <!-- Week view -->
106
+ <div v-else-if="viewMode === SCHEDULER_VIEW.week" class="flex-1 overflow-y-auto min-h-0">
107
+ <div class="grid grid-cols-7 border-b border-gray-200">
108
+ <div v-for="day in weekDays" :key="day.toISOString()" class="border-r last:border-r-0 border-gray-200 min-h-[200px] flex flex-col">
109
+ <!-- Day header -->
110
+ <div class="px-2 py-1.5 text-center border-b border-gray-100 sticky top-0 bg-white" :class="isToday(day) ? 'bg-blue-50' : ''">
111
+ <div class="text-xs text-gray-400">{{ dayLabel(day) }}</div>
112
+ <div
113
+ class="text-sm font-medium"
114
+ :class="isToday(day) ? 'text-blue-600 bg-blue-500 text-white w-6 h-6 rounded-full flex items-center justify-center mx-auto' : 'text-gray-700'"
115
+ >
116
+ {{ day.getDate() }}
117
+ </div>
118
+ </div>
119
+ <!-- Day items -->
120
+ <div class="flex-1 p-1 space-y-0.5">
121
+ <div
122
+ v-for="item in itemsForDay(day)"
123
+ :key="item.id"
124
+ class="text-xs px-1.5 py-0.5 rounded cursor-pointer truncate"
125
+ :class="selectedId === item.id ? 'bg-blue-500 text-white' : 'bg-blue-100 text-blue-800 hover:bg-blue-200'"
126
+ :title="item.title"
127
+ @click="selectItem(item)"
128
+ >
129
+ <span v-if="itemTime(item)" class="font-medium">{{ itemTime(item) }} </span>{{ item.title }}
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ <!-- Unscheduled -->
135
+ <div v-if="unscheduledItems.length > 0" class="p-3 border-t border-gray-200">
136
+ <div class="text-xs text-gray-400 mb-1.5">Unscheduled</div>
137
+ <div class="flex flex-wrap gap-1">
138
+ <div
139
+ v-for="item in unscheduledItems"
140
+ :key="item.id"
141
+ class="text-xs px-2 py-1 rounded cursor-pointer"
142
+ :class="selectedId === item.id ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
143
+ @click="selectItem(item)"
144
+ >
145
+ {{ item.title }}
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Month view -->
152
+ <div v-else class="flex-1 overflow-y-auto min-h-0">
153
+ <!-- Weekday headers -->
154
+ <div class="grid grid-cols-7 border-b border-gray-200 sticky top-0 bg-white z-10">
155
+ <div v-for="label in WEEKDAY_LABELS" :key="label" class="text-xs text-center text-gray-400 py-1.5 border-r last:border-r-0 border-gray-100">
156
+ {{ label }}
157
+ </div>
158
+ </div>
159
+ <!-- Month grid -->
160
+ <div v-for="(week, wi) in monthGrid" :key="wi" class="grid grid-cols-7 border-b border-gray-100">
161
+ <div
162
+ v-for="day in week"
163
+ :key="day.toISOString()"
164
+ class="border-r last:border-r-0 border-gray-100 min-h-[80px] p-1 flex flex-col"
165
+ :class="isToday(day) ? 'bg-blue-50/50' : ''"
166
+ >
167
+ <div class="text-xs mb-0.5" :class="isCurrentMonth(day) ? (isToday(day) ? 'text-blue-600 font-bold' : 'text-gray-700') : 'text-gray-300'">
168
+ {{ day.getDate() }}
169
+ </div>
170
+ <div class="space-y-0.5 flex-1">
171
+ <div
172
+ v-for="item in itemsForDay(day).slice(0, MAX_MONTH_ITEMS)"
173
+ :key="item.id"
174
+ class="text-[10px] leading-tight px-1 py-0.5 rounded cursor-pointer truncate"
175
+ :class="selectedId === item.id ? 'bg-blue-500 text-white' : 'bg-blue-100 text-blue-800 hover:bg-blue-200'"
176
+ :title="item.title"
177
+ @click="selectItem(item)"
178
+ >
179
+ {{ item.title }}
180
+ </div>
181
+ <div v-if="itemsForDay(day).length > MAX_MONTH_ITEMS" class="text-[10px] text-gray-400 px-1">
182
+ +{{ itemsForDay(day).length - MAX_MONTH_ITEMS }} more
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ <!-- Unscheduled -->
188
+ <div v-if="unscheduledItems.length > 0" class="p-3 border-t border-gray-200">
189
+ <div class="text-xs text-gray-400 mb-1.5">Unscheduled</div>
190
+ <div class="flex flex-wrap gap-1">
191
+ <div
192
+ v-for="item in unscheduledItems"
193
+ :key="item.id"
194
+ class="text-xs px-2 py-1 rounded cursor-pointer"
195
+ :class="selectedId === item.id ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
196
+ @click="selectItem(item)"
197
+ >
198
+ {{ item.title }}
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </div>
203
+
204
+ <!-- Item YAML editor -->
205
+ <div v-if="selectedId" class="border-t border-blue-200 bg-blue-50 shrink-0">
206
+ <div class="flex items-center justify-between px-4 py-2 text-sm font-medium text-blue-700">
207
+ <span>Edit item</span>
208
+ <button class="text-blue-400 hover:text-blue-600 text-xs" title="Close editor" @click="selectedId = null">✕</button>
209
+ </div>
210
+ <div class="px-3 pb-3">
211
+ <textarea
212
+ v-model="yamlText"
213
+ class="w-full h-32 p-3 font-mono text-xs bg-white border border-blue-300 rounded resize-y focus:outline-none focus:border-blue-500"
214
+ spellcheck="false"
215
+ />
216
+ <div class="flex items-center gap-2 mt-2">
217
+ <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="applyItemEdit">Update</button>
218
+ <span v-if="yamlError" class="text-xs text-red-500">{{ yamlError }}</span>
219
+ </div>
220
+ </div>
221
+ </div>
222
+
223
+ <!-- JSON source editor -->
224
+ <details class="border-t border-gray-200 bg-gray-50 shrink-0">
225
+ <summary class="cursor-pointer select-none px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100">Edit Source</summary>
226
+ <div class="p-3">
227
+ <textarea
228
+ v-model="editorText"
229
+ class="w-full h-[40vh] p-3 font-mono text-xs bg-white border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
230
+ spellcheck="false"
231
+ />
232
+ <div class="flex items-center gap-2 mt-2">
233
+ <button
234
+ :disabled="!isModified"
235
+ class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed"
236
+ @click="applyChanges"
237
+ >
238
+ Apply Changes
239
+ </button>
240
+ <span v-if="parseError" class="text-xs text-red-500">{{ parseError }}</span>
241
+ </div>
242
+ </div>
243
+ </details>
244
+ </template>
245
+ </div>
246
+ </template>
247
+
248
+ <script setup lang="ts">
249
+ import { computed, ref, watch } from "vue";
250
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
251
+ import type { SchedulerData, ScheduledItem } from "./index";
252
+ import { useFreshPluginData } from "../../composables/useFreshPluginData";
253
+ import { apiPost } from "../../utils/api";
254
+ import { API_ROUTES } from "../../config/apiRoutes";
255
+ import TasksTab from "./TasksTab.vue";
256
+ import { isToday } from "../../utils/format/date";
257
+
258
+ type YamlScalar = string | number | boolean | null;
259
+
260
+ const props = defineProps<{
261
+ selectedResult?: ToolResultComplete<SchedulerData>;
262
+ }>();
263
+ const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
264
+
265
+ function detectInitialTab(result?: ToolResultComplete<SchedulerData>): SchedulerTab {
266
+ const data = result?.data as Record<string, unknown> | undefined;
267
+ if (data && ("task" in data || "tasks" in data || "triggered" in data || "deleted" in data)) {
268
+ return SCHEDULER_TAB.tasks;
269
+ }
270
+ return SCHEDULER_TAB.calendar;
271
+ }
272
+
273
+ const activeTab = ref<SchedulerTab>(detectInitialTab(props.selectedResult));
274
+ const items = ref<ScheduledItem[]>(props.selectedResult?.data?.items ?? []);
275
+
276
+ const { refresh } = useFreshPluginData<ScheduledItem[]>({
277
+ endpoint: () => API_ROUTES.scheduler.base,
278
+ extract: (json) => {
279
+ const payload = (json as { data?: { items?: ScheduledItem[] } }).data?.items;
280
+ return Array.isArray(payload) ? payload : null;
281
+ },
282
+ apply: (data) => {
283
+ items.value = data;
284
+ },
285
+ });
286
+
287
+ watch(
288
+ () => props.selectedResult?.uuid,
289
+ () => {
290
+ activeTab.value = detectInitialTab(props.selectedResult);
291
+ items.value = props.selectedResult?.data?.items ?? [];
292
+ void refresh();
293
+ },
294
+ );
295
+
296
+ // ── View mode ──────────────────────────────────────────────────────────────
297
+
298
+ import { SCHEDULER_VIEW, SCHEDULER_VIEW_MODES as VIEW_MODES, SCHEDULER_TAB, type SchedulerViewMode as ViewMode, type SchedulerTab } from "./viewModes";
299
+
300
+ const WEEKDAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
301
+ const MAX_MONTH_ITEMS = 3;
302
+
303
+ const viewMode = ref<ViewMode>(SCHEDULER_VIEW.month);
304
+ const currentDate = ref(new Date());
305
+
306
+ // ── Calendar utilities ─────────────────────────────────────────────────────
307
+
308
+ function startOfWeek(date: Date): Date {
309
+ const result = new Date(date);
310
+ const day = result.getDay();
311
+ const diff = day === 0 ? -6 : 1 - day; // Monday start
312
+ result.setDate(result.getDate() + diff);
313
+ result.setHours(0, 0, 0, 0);
314
+ return result;
315
+ }
316
+
317
+ function getWeekDays(date: Date): Date[] {
318
+ const start = startOfWeek(date);
319
+ return Array.from({ length: 7 }, (__unused, i) => {
320
+ const next = new Date(start);
321
+ next.setDate(start.getDate() + i);
322
+ return next;
323
+ });
324
+ }
325
+
326
+ function getMonthGrid(year: number, month: number): Date[][] {
327
+ const firstDay = new Date(year, month, 1);
328
+ const start = startOfWeek(firstDay);
329
+ const weeks: Date[][] = [];
330
+ const WEEK_COUNT = 6;
331
+ for (let weekIdx = 0; weekIdx < WEEK_COUNT; weekIdx++) {
332
+ const week: Date[] = [];
333
+ for (let dayIdx = 0; dayIdx < 7; dayIdx++) {
334
+ const date = new Date(start);
335
+ date.setDate(start.getDate() + weekIdx * 7 + dayIdx);
336
+ week.push(date);
337
+ }
338
+ weeks.push(week);
339
+ }
340
+ return weeks;
341
+ }
342
+
343
+ function isCurrentMonth(date: Date): boolean {
344
+ return date.getMonth() === currentDate.value.getMonth() && date.getFullYear() === currentDate.value.getFullYear();
345
+ }
346
+
347
+ function toDateString(date: Date): string {
348
+ const year = date.getFullYear();
349
+ const month = String(date.getMonth() + 1).padStart(2, "0");
350
+ const day = String(date.getDate()).padStart(2, "0");
351
+ return `${year}-${month}-${day}`;
352
+ }
353
+
354
+ function itemsForDay(day: Date): ScheduledItem[] {
355
+ const dateStr = toDateString(day);
356
+ return items.value.filter((item) => String(item.props.date) === dateStr);
357
+ }
358
+
359
+ const unscheduledItems = computed(() => items.value.filter((item) => !item.props.date));
360
+
361
+ function itemTime(item: ScheduledItem): string {
362
+ const time = item.props.time;
363
+ return typeof time === "string" ? time : "";
364
+ }
365
+
366
+ function dayLabel(date: Date): string {
367
+ return WEEKDAY_LABELS[date.getDay() === 0 ? 6 : date.getDay() - 1];
368
+ }
369
+
370
+ // ── Navigation ─────────────────────────────────────────────────────────────
371
+
372
+ const weekDays = computed(() => getWeekDays(currentDate.value));
373
+
374
+ const monthGrid = computed(() => getMonthGrid(currentDate.value.getFullYear(), currentDate.value.getMonth()));
375
+
376
+ const headerLabel = computed(() => {
377
+ if (viewMode.value === "week") {
378
+ const days = weekDays.value;
379
+ const fmt = (date: Date) => date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
380
+ return `${fmt(days[0])} – ${fmt(days[6])}, ${days[0].getFullYear()}`;
381
+ }
382
+ return currentDate.value.toLocaleDateString(undefined, {
383
+ month: "long",
384
+ year: "numeric",
385
+ });
386
+ });
387
+
388
+ function goToday() {
389
+ currentDate.value = new Date();
390
+ }
391
+
392
+ function goPrev() {
393
+ const next = new Date(currentDate.value);
394
+ if (viewMode.value === "week") {
395
+ next.setDate(next.getDate() - 7);
396
+ } else {
397
+ next.setMonth(next.getMonth() - 1);
398
+ }
399
+ currentDate.value = next;
400
+ }
401
+
402
+ function goNext() {
403
+ const next = new Date(currentDate.value);
404
+ if (viewMode.value === "week") {
405
+ next.setDate(next.getDate() + 7);
406
+ } else {
407
+ next.setMonth(next.getMonth() + 1);
408
+ }
409
+ currentDate.value = next;
410
+ }
411
+
412
+ // ── YAML helpers ────────────────────────────────────────────────────────────
413
+
414
+ function yamlStringValue(raw: string): string {
415
+ const needsQuotes = raw === "" || /[:#[\]{},&*?|<>=!%@`]/.test(raw) || /^\s|\s$/.test(raw) || /^(true|false|null|~)$/i.test(raw) || /^\d/.test(raw);
416
+ if (needsQuotes) {
417
+ return `"${raw.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
418
+ }
419
+ return raw;
420
+ }
421
+
422
+ function serializeYaml(item: ScheduledItem): string {
423
+ const lines: string[] = [`title: ${yamlStringValue(item.title)}`];
424
+ for (const [key, value] of Object.entries(item.props)) {
425
+ if (value === null) continue;
426
+ if (typeof value === "string") {
427
+ lines.push(`${key}: ${yamlStringValue(value)}`);
428
+ } else {
429
+ lines.push(`${key}: ${value}`);
430
+ }
431
+ }
432
+ return lines.join("\n");
433
+ }
434
+
435
+ function parseYamlValue(raw: string): YamlScalar {
436
+ if (raw === "null" || raw === "~") return null;
437
+ if (raw === "true") return true;
438
+ if (raw === "false") return false;
439
+ if (raw.startsWith('"') && raw.endsWith('"')) {
440
+ return raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
441
+ }
442
+ if (raw.startsWith("'") && raw.endsWith("'")) {
443
+ return raw.slice(1, -1);
444
+ }
445
+ const num = Number(raw);
446
+ if (raw !== "" && !isNaN(num)) return num;
447
+ return raw;
448
+ }
449
+
450
+ function parseYaml(text: string): {
451
+ title: string;
452
+ props: Record<string, string | number | boolean | null>;
453
+ } | null {
454
+ const result: Record<string, string | number | boolean | null> = {};
455
+ for (const line of text.split("\n")) {
456
+ const trimmed = line.trim();
457
+ if (!trimmed || trimmed.startsWith("#")) continue;
458
+ const colonIdx = line.indexOf(": ");
459
+ if (colonIdx === -1) continue;
460
+ const key = line.slice(0, colonIdx).trim();
461
+ const rawVal = line.slice(colonIdx + 2).trim();
462
+ result[key] = parseYamlValue(rawVal);
463
+ }
464
+ const title = result["title"];
465
+ if (typeof title !== "string" || !title) return null;
466
+ const itemProps = { ...result };
467
+ delete itemProps["title"];
468
+ return { title, props: itemProps };
469
+ }
470
+
471
+ // ── Item selection & YAML edit ───────────────────────────────────────────────
472
+
473
+ const selectedId = ref<string | null>(null);
474
+ const yamlText = ref("");
475
+ const yamlError = ref("");
476
+
477
+ function selectItem(item: ScheduledItem) {
478
+ if (selectedId.value === item.id) {
479
+ selectedId.value = null;
480
+ return;
481
+ }
482
+ selectedId.value = item.id;
483
+ yamlText.value = serializeYaml(item);
484
+ yamlError.value = "";
485
+ }
486
+
487
+ watch(items, () => {
488
+ if (selectedId.value) {
489
+ const item = items.value.find((i) => i.id === selectedId.value);
490
+ if (item) {
491
+ yamlText.value = serializeYaml(item);
492
+ } else {
493
+ selectedId.value = null;
494
+ }
495
+ }
496
+ editorText.value = toJson(items.value);
497
+ parseError.value = "";
498
+ });
499
+
500
+ async function applyItemEdit() {
501
+ yamlError.value = "";
502
+ const parsed = parseYaml(yamlText.value);
503
+ if (!parsed) {
504
+ yamlError.value = "Could not parse YAML — ensure 'title' is present";
505
+ return;
506
+ }
507
+ const success = await callApi({
508
+ action: "update",
509
+ id: selectedId.value,
510
+ title: parsed.title,
511
+ props: parsed.props,
512
+ });
513
+ if (success) selectedId.value = null;
514
+ }
515
+
516
+ // ── JSON source editor ───────────────────────────────────────────────────────
517
+
518
+ function toJson(its: ScheduledItem[]) {
519
+ return JSON.stringify(its, null, 2);
520
+ }
521
+
522
+ const editorText = ref(toJson(items.value));
523
+ const parseError = ref("");
524
+ // Last POST /api/scheduler failure. Cleared on the next successful call
525
+ // so the banner disappears as soon as things recover.
526
+ const apiError = ref<string | null>(null);
527
+ const isModified = computed(() => editorText.value !== toJson(items.value));
528
+
529
+ async function callApi(body: Record<string, unknown>): Promise<boolean> {
530
+ const response = await apiPost<{ data?: { items?: ScheduledItem[] } }>(API_ROUTES.scheduler.base, body);
531
+ if (!response.ok) {
532
+ apiError.value = response.error;
533
+ return false;
534
+ }
535
+ apiError.value = null;
536
+ const result = response.data;
537
+ items.value = result.data?.items ?? [];
538
+ if (props.selectedResult) {
539
+ emit("updateResult", {
540
+ ...props.selectedResult,
541
+ ...result,
542
+ uuid: props.selectedResult.uuid,
543
+ });
544
+ }
545
+ return true;
546
+ }
547
+
548
+ async function remove(item: ScheduledItem): Promise<void> {
549
+ if (selectedId.value === item.id) selectedId.value = null;
550
+ await callApi({ action: "delete", id: item.id });
551
+ }
552
+
553
+ async function applyChanges() {
554
+ parseError.value = "";
555
+ let parsed: ScheduledItem[];
556
+ try {
557
+ parsed = JSON.parse(editorText.value);
558
+ if (!Array.isArray(parsed)) throw new Error("Expected a JSON array");
559
+ } catch (err) {
560
+ parseError.value = err instanceof Error ? err.message : "Invalid JSON";
561
+ return;
562
+ }
563
+ callApi({ action: "replace", items: parsed });
564
+ }
565
+ </script>
@@ -0,0 +1,57 @@
1
+ import type { ToolDefinition } from "gui-chat-protocol";
2
+ import { SCHEDULER_ACTIONS } from "../../config/schedulerActions";
3
+
4
+ const toolDefinition: ToolDefinition = {
5
+ type: "function",
6
+ name: "manageScheduler",
7
+ prompt:
8
+ "When users mention events, appointments, meetings, reminders, or recurring tasks to schedule, use manageScheduler. " +
9
+ "For calendar events use show/add/update/delete. " +
10
+ "For automated recurring tasks (e.g. '毎朝8時にニュースまとめて', 'remind me every day') use createTask/listTasks/deleteTask/runTask. " +
11
+ "Schedule format: { type: 'interval', intervalMs: 3600000 } for hourly, { type: 'daily', time: 'HH:MM' } for daily (UTC).",
12
+ description:
13
+ "Manage a scheduler — calendar events (show/add/update/delete) and automated tasks (createTask/listTasks/deleteTask/runTask). " +
14
+ "Calendar items have a title and properties. Automated tasks have a name, prompt, schedule, and run via the agent on a recurring basis.",
15
+ parameters: {
16
+ type: "object",
17
+ properties: {
18
+ action: {
19
+ type: "string",
20
+ enum: Object.values(SCHEDULER_ACTIONS),
21
+ description: "Action to perform. show/add/delete/update for calendar items. createTask/listTasks/deleteTask/runTask for automated tasks.",
22
+ },
23
+ title: {
24
+ type: "string",
25
+ description: "For 'add': the item title. For 'update': new title (optional).",
26
+ },
27
+ id: {
28
+ type: "string",
29
+ description: "For 'delete', 'update', 'deleteTask', 'runTask': the item/task id.",
30
+ },
31
+ props: {
32
+ type: "object",
33
+ description: "For 'add': initial properties (e.g. { date, time, location }). For 'update': properties to merge in; set a key to null to remove it.",
34
+ additionalProperties: true,
35
+ },
36
+ name: {
37
+ type: "string",
38
+ description: "For 'createTask': the task name.",
39
+ },
40
+ prompt: {
41
+ type: "string",
42
+ description: "For 'createTask': the prompt message sent to the agent when the task fires.",
43
+ },
44
+ schedule: {
45
+ type: "object",
46
+ description: "For 'createTask': { type: 'daily', time: 'HH:MM' } or { type: 'interval', intervalMs: number }. Times are UTC.",
47
+ },
48
+ roleId: {
49
+ type: "string",
50
+ description: "For 'createTask': role to use (default: general).",
51
+ },
52
+ },
53
+ required: ["action"],
54
+ },
55
+ };
56
+
57
+ export default toolDefinition;
@@ -0,0 +1,45 @@
1
+ import type { ToolPlugin } from "../../tools/types";
2
+ import type { ToolResult } from "gui-chat-protocol";
3
+ import View from "./View.vue";
4
+ import Preview from "./Preview.vue";
5
+ import toolDefinition from "./definition";
6
+ import { apiPost } from "../../utils/api";
7
+ import { API_ROUTES } from "../../config/apiRoutes";
8
+
9
+ export interface ScheduledItem {
10
+ id: string;
11
+ title: string;
12
+ createdAt: number;
13
+ props: Record<string, string | number | boolean | null>;
14
+ }
15
+
16
+ export interface SchedulerData {
17
+ items: ScheduledItem[];
18
+ }
19
+
20
+ const schedulerPlugin: ToolPlugin<SchedulerData> = {
21
+ toolDefinition,
22
+
23
+ async execute(_context, args) {
24
+ const result = await apiPost<ToolResult<SchedulerData>>(API_ROUTES.scheduler.base, args);
25
+ if (!result.ok) {
26
+ return {
27
+ toolName: "manageScheduler",
28
+ uuid: crypto.randomUUID(),
29
+ message: result.error,
30
+ };
31
+ }
32
+ return {
33
+ ...result.data,
34
+ toolName: "manageScheduler",
35
+ uuid: result.data.uuid ?? crypto.randomUUID(),
36
+ };
37
+ },
38
+
39
+ isEnabled: () => true,
40
+ generatingMessage: "Managing schedule...",
41
+ viewComponent: View,
42
+ previewComponent: Preview,
43
+ };
44
+
45
+ export default schedulerPlugin;