mulmoclaude 0.5.2 → 0.6.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 (486) hide show
  1. package/Dockerfile.sandbox +100 -0
  2. package/README.md +17 -4
  3. package/bin/mulmoclaude.js +46 -15
  4. package/bin/prepare-dist.js +18 -2
  5. package/client/assets/chunk-CernVdwh.js +1 -0
  6. package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
  7. package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
  8. package/client/assets/index-BbgSjFQ8.js +4968 -0
  9. package/client/assets/index-ECD0lgIv.css +2 -0
  10. package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
  11. package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
  12. package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
  13. package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
  14. package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
  15. package/client/assets/vue-1e_vz2LW.js +1 -0
  16. package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
  17. package/client/index.html +33 -2
  18. package/package.json +20 -18
  19. package/sandbox-entrypoint.sh +106 -0
  20. package/server/accounting/accountNormalize.ts +32 -0
  21. package/server/accounting/defaultAccounts.ts +87 -0
  22. package/server/accounting/eventPublisher.ts +51 -0
  23. package/server/accounting/journal.ts +252 -0
  24. package/server/accounting/openingBalances.ts +114 -0
  25. package/server/accounting/report.ts +237 -0
  26. package/server/accounting/service.ts +718 -0
  27. package/server/accounting/snapshotCache.ts +333 -0
  28. package/server/accounting/timeSeries.ts +265 -0
  29. package/server/accounting/types.ts +148 -0
  30. package/server/agent/activeTools.ts +128 -0
  31. package/server/agent/attachmentConverter.ts +10 -5
  32. package/server/agent/backend/claude-code.ts +8 -2
  33. package/server/agent/backend/types.ts +1 -1
  34. package/server/agent/config.ts +101 -31
  35. package/server/agent/index.ts +45 -33
  36. package/server/agent/mcp-server.ts +146 -69
  37. package/server/agent/mcp-tools/index.ts +1 -5
  38. package/server/agent/mcp-tools/notify.ts +2 -22
  39. package/server/agent/mcp-tools/x.ts +0 -4
  40. package/server/agent/mcpHealth.ts +168 -0
  41. package/server/agent/plugin-names.ts +20 -77
  42. package/server/agent/prompt.ts +259 -51
  43. package/server/agent/resumeFailover.ts +1 -1
  44. package/server/agent/stream.ts +0 -1
  45. package/server/api/auth/bearerAuth.ts +5 -5
  46. package/server/api/csrfGuard.ts +1 -1
  47. package/server/api/routes/accounting.ts +366 -0
  48. package/server/api/routes/agent.ts +509 -46
  49. package/server/api/routes/attachment.ts +104 -0
  50. package/server/api/routes/chart.ts +2 -1
  51. package/server/api/routes/config.ts +12 -12
  52. package/server/api/routes/files.ts +105 -48
  53. package/server/api/routes/image.ts +70 -25
  54. package/server/api/routes/journal.ts +35 -0
  55. package/server/api/routes/mulmo-script.ts +358 -118
  56. package/server/api/routes/mulmoScriptValidate.ts +1 -1
  57. package/server/api/routes/news.ts +1 -1
  58. package/server/api/routes/notifications.ts +92 -22
  59. package/server/api/routes/notifier.ts +98 -0
  60. package/server/api/routes/pdf.ts +188 -48
  61. package/server/api/routes/plugins.ts +34 -14
  62. package/server/api/routes/presentHtml.ts +58 -3
  63. package/server/api/routes/roles.ts +1 -8
  64. package/server/api/routes/runtime-plugin.ts +224 -0
  65. package/server/api/routes/scheduler.ts +7 -5
  66. package/server/api/routes/schedulerHandlers.ts +1 -1
  67. package/server/api/routes/schedulerTasks.ts +8 -7
  68. package/server/api/routes/sessions.ts +234 -121
  69. package/server/api/routes/skills.ts +56 -51
  70. package/server/api/routes/sources.ts +52 -45
  71. package/server/api/routes/translation.ts +44 -0
  72. package/server/api/routes/wiki/frontmatter.ts +13 -65
  73. package/server/api/routes/wiki/history.ts +261 -0
  74. package/server/api/routes/wiki/pageIndex.ts +1 -1
  75. package/server/api/routes/wiki.ts +50 -26
  76. package/server/events/file-change.ts +83 -0
  77. package/server/events/notifications.ts +247 -91
  78. package/server/events/pub-sub/index.ts +1 -1
  79. package/server/events/relay-client.ts +5 -5
  80. package/server/events/scheduler-adapter.ts +2 -2
  81. package/server/events/session-store/index.ts +110 -22
  82. package/server/events/task-manager/index.ts +10 -9
  83. package/server/index.ts +509 -33
  84. package/server/notifier/engine.ts +419 -0
  85. package/server/notifier/legacy-adapters.ts +76 -0
  86. package/server/notifier/runtime-api.ts +74 -0
  87. package/server/notifier/store.ts +70 -0
  88. package/server/notifier/types.ts +121 -0
  89. package/server/plugins/dev-loader.ts +171 -0
  90. package/server/plugins/dev-watcher.ts +150 -0
  91. package/server/plugins/diagnostics.ts +188 -0
  92. package/server/plugins/preset-list.ts +52 -0
  93. package/server/plugins/preset-loader.ts +112 -0
  94. package/server/plugins/runtime-chat-api.ts +38 -0
  95. package/server/plugins/runtime-loader.ts +430 -0
  96. package/server/plugins/runtime-registry.ts +112 -0
  97. package/server/plugins/runtime-tasks-api.ts +50 -0
  98. package/server/plugins/runtime.ts +378 -0
  99. package/server/services/translation/cache.ts +72 -0
  100. package/server/services/translation/index.ts +106 -0
  101. package/server/services/translation/llm.ts +140 -0
  102. package/server/services/translation/types.ts +35 -0
  103. package/server/system/credentials.ts +13 -2
  104. package/server/system/env.ts +6 -1
  105. package/server/system/logger/formatters.ts +46 -4
  106. package/server/system/logger/index.ts +4 -4
  107. package/server/system/logger/sinks.ts +26 -5
  108. package/server/system/logger/types.ts +2 -2
  109. package/server/utils/dev-plugin-args.d.mts +11 -0
  110. package/server/utils/dev-plugin-args.mjs +43 -0
  111. package/server/utils/errors.ts +13 -4
  112. package/server/utils/files/accounting-io.ts +295 -0
  113. package/server/utils/files/atomic.ts +17 -49
  114. package/server/utils/files/attachment-store.ts +182 -0
  115. package/server/utils/files/html-io.ts +1 -7
  116. package/server/utils/files/html-store.ts +19 -0
  117. package/server/utils/files/image-store.ts +20 -22
  118. package/server/utils/files/index.ts +5 -15
  119. package/server/utils/files/journal-io.ts +7 -35
  120. package/server/utils/files/json.ts +2 -29
  121. package/server/utils/files/markdown-image-fill.ts +6 -37
  122. package/server/utils/files/markdown-store.ts +6 -21
  123. package/server/utils/files/naming.ts +3 -39
  124. package/server/utils/files/plugins-io.ts +100 -0
  125. package/server/utils/files/reference-dirs-io.ts +1 -9
  126. package/server/utils/files/roles-io.ts +2 -10
  127. package/server/utils/files/safe.ts +17 -19
  128. package/server/utils/files/scheduler-io.ts +1 -7
  129. package/server/utils/files/scheduler-overrides-io.ts +3 -12
  130. package/server/utils/files/session-io.ts +21 -30
  131. package/server/utils/files/spreadsheet-store.ts +9 -22
  132. package/server/utils/files/translation-io.ts +46 -0
  133. package/server/utils/files/user-tasks-io.ts +1 -7
  134. package/server/utils/files/workspace-io.ts +3 -79
  135. package/server/utils/gemini.ts +33 -11
  136. package/server/utils/html/htmlArtifactSplicer.ts +41 -0
  137. package/server/utils/markdown/frontmatter.ts +112 -0
  138. package/server/utils/regex.ts +56 -0
  139. package/server/utils/router.ts +41 -0
  140. package/server/utils/slug.ts +5 -3
  141. package/server/utils/time.ts +12 -0
  142. package/server/workspace/chat-index/indexer.ts +15 -2
  143. package/server/workspace/chat-index/summarizer.ts +1 -1
  144. package/server/workspace/custom-dirs.ts +1 -1
  145. package/server/workspace/helps/gemini.md +1 -1
  146. package/server/workspace/helps/guide.md +61 -0
  147. package/server/workspace/helps/index.md +4 -0
  148. package/server/workspace/helps/presenthtml.md +80 -0
  149. package/server/workspace/helps/sandbox.md +7 -0
  150. package/server/workspace/helps/storyteller.md +101 -0
  151. package/server/workspace/helps/telegram.md +1 -0
  152. package/server/workspace/helps/wiki.md +9 -7
  153. package/server/workspace/journal/archivist-cli.ts +7 -33
  154. package/server/workspace/journal/archivist-schemas.ts +5 -43
  155. package/server/workspace/journal/dailyPass.ts +34 -187
  156. package/server/workspace/journal/diff.ts +3 -28
  157. package/server/workspace/journal/index.ts +10 -81
  158. package/server/workspace/journal/indexFile.ts +3 -24
  159. package/server/workspace/journal/latestDaily.ts +51 -0
  160. package/server/workspace/journal/memoryExtractor.ts +4 -20
  161. package/server/workspace/journal/optimizationPass.ts +4 -21
  162. package/server/workspace/journal/paths.ts +4 -23
  163. package/server/workspace/journal/state.ts +6 -29
  164. package/server/workspace/memory/io.ts +213 -0
  165. package/server/workspace/memory/llm-classifier.ts +158 -0
  166. package/server/workspace/memory/migrate.ts +263 -0
  167. package/server/workspace/memory/run.ts +84 -0
  168. package/server/workspace/memory/topic-cluster.ts +218 -0
  169. package/server/workspace/memory/topic-detect.ts +67 -0
  170. package/server/workspace/memory/topic-index-hook.ts +128 -0
  171. package/server/workspace/memory/topic-io.ts +180 -0
  172. package/server/workspace/memory/topic-migrate.ts +248 -0
  173. package/server/workspace/memory/topic-run.ts +172 -0
  174. package/server/workspace/memory/topic-swap.ts +135 -0
  175. package/server/workspace/memory/topic-types.ts +142 -0
  176. package/server/workspace/memory/types.ts +83 -0
  177. package/server/workspace/news/reader.ts +4 -5
  178. package/server/workspace/paths.ts +124 -47
  179. package/server/workspace/roles.ts +2 -11
  180. package/server/workspace/skills/parser.ts +38 -55
  181. package/server/workspace/skills/user-tasks.ts +1 -2
  182. package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
  183. package/server/workspace/skills-preset.ts +196 -0
  184. package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
  185. package/server/workspace/sources/fetchers/index.ts +1 -1
  186. package/server/workspace/sources/fetchers/rssParser.ts +1 -1
  187. package/server/workspace/sources/pipeline/index.ts +2 -2
  188. package/server/workspace/sources/pipeline/notify.ts +3 -3
  189. package/server/workspace/sources/pipeline/write.ts +2 -2
  190. package/server/workspace/sources/registry.ts +39 -61
  191. package/server/workspace/sources/robots.ts +1 -1
  192. package/server/workspace/tool-trace/classify.ts +2 -1
  193. package/server/workspace/tool-trace/index.ts +1 -1
  194. package/server/workspace/tool-trace/writeSearch.ts +6 -1
  195. package/server/workspace/wiki-backlinks/index.ts +19 -7
  196. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
  197. package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
  198. package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
  199. package/server/workspace/wiki-history/provision.ts +181 -0
  200. package/server/workspace/wiki-pages/io.ts +217 -0
  201. package/server/workspace/wiki-pages/snapshot.ts +380 -0
  202. package/server/workspace/workspace.ts +75 -13
  203. package/src/App.vue +115 -40
  204. package/src/_runtime/protocol-vue.ts +21 -0
  205. package/src/_runtime/vue.ts +22 -0
  206. package/src/components/ChatInput.vue +14 -10
  207. package/src/components/CopyChatButton.vue +76 -0
  208. package/src/components/FileContentRenderer.vue +67 -14
  209. package/src/components/FileTree.vue +2 -2
  210. package/src/components/FilesView.vue +17 -1
  211. package/src/components/NewsView.vue +16 -2
  212. package/src/components/NotificationBell.vue +320 -93
  213. package/src/components/PageChatComposer.vue +5 -4
  214. package/src/components/PluginLauncher.vue +42 -6
  215. package/src/components/PluginScopedRoot.vue +87 -0
  216. package/src/components/RoleSelector.vue +12 -1
  217. package/src/components/RolesView.vue +562 -0
  218. package/src/components/SentAttachmentChip.vue +102 -0
  219. package/src/components/SessionHistoryPanel.vue +109 -20
  220. package/src/components/SessionRoleIcon.vue +7 -4
  221. package/src/components/SessionSidebar.vue +20 -7
  222. package/src/components/SessionTabBar.vue +1 -1
  223. package/src/components/SettingsMcpTab.vue +4 -4
  224. package/src/components/SettingsModal.vue +2 -0
  225. package/src/components/SidebarHeader.vue +16 -5
  226. package/src/components/SourcesManager.vue +23 -9
  227. package/src/components/SourcesView.vue +1 -1
  228. package/src/components/StackView.vue +102 -6
  229. package/src/components/SuggestionsPanel.vue +105 -16
  230. package/src/components/SystemFileBanner.vue +1 -1
  231. package/src/components/TodoExplorer.vue +4 -5
  232. package/src/components/todo/TodoAddDialog.vue +2 -3
  233. package/src/components/todo/TodoEditDialog.vue +1 -2
  234. package/src/components/todo/TodoEditPanel.vue +2 -3
  235. package/src/components/todo/TodoKanbanView.vue +8 -5
  236. package/src/components/todo/TodoListView.vue +3 -5
  237. package/src/components/todo/TodoTableView.vue +7 -5
  238. package/src/composables/useAccountingChannel.ts +58 -0
  239. package/src/composables/useActiveSession.ts +4 -25
  240. package/src/composables/useAppApi.ts +6 -44
  241. package/src/composables/useClipboardCopy.ts +3 -20
  242. package/src/composables/useContentDisplay.ts +33 -2
  243. package/src/composables/useDevPluginReload.ts +23 -0
  244. package/src/composables/useDynamicFavicon.ts +5 -31
  245. package/src/composables/useEventListeners.ts +0 -20
  246. package/src/composables/useExpandedDirs.ts +4 -15
  247. package/src/composables/useFaviconState.ts +12 -46
  248. package/src/composables/useFileChange.ts +53 -0
  249. package/src/composables/useFreshPluginData.ts +6 -43
  250. package/src/composables/useHealth.ts +14 -43
  251. package/src/composables/useImageErrorRepair.ts +104 -0
  252. package/src/composables/useLatestDaily.ts +40 -0
  253. package/src/composables/useMarkdownDoc.ts +39 -0
  254. package/src/composables/useMarkdownLinkHandler.ts +1 -1
  255. package/src/composables/useMcpTools.ts +3 -16
  256. package/src/composables/useNotifications.ts +138 -112
  257. package/src/composables/usePdfDownload.ts +17 -3
  258. package/src/composables/usePendingCalls.ts +8 -26
  259. package/src/composables/usePluginErrorBoundary.ts +68 -0
  260. package/src/composables/usePubSub.ts +9 -17
  261. package/src/composables/useRunElapsed.ts +5 -22
  262. package/src/composables/useSandboxStatus.ts +4 -20
  263. package/src/composables/useSessionDerived.ts +7 -15
  264. package/src/composables/useSessionHistory.ts +70 -29
  265. package/src/composables/useSessionSync.ts +25 -3
  266. package/src/composables/useSkillsList.ts +59 -0
  267. package/src/composables/useTranslatedQueries.ts +109 -0
  268. package/src/config/apiRoutes.ts +181 -80
  269. package/src/config/historyFilters.ts +5 -3
  270. package/src/config/hostEvents.ts +17 -0
  271. package/src/config/mcpCatalog.ts +277 -5
  272. package/src/config/pubsubChannels.ts +134 -12
  273. package/src/config/roles.ts +212 -147
  274. package/src/config/systemFileDescriptors.ts +5 -5
  275. package/src/config/toolNames.ts +52 -30
  276. package/src/config/workspacePaths.ts +26 -2
  277. package/src/lang/de.ts +483 -27
  278. package/src/lang/en.ts +448 -27
  279. package/src/lang/es.ts +474 -27
  280. package/src/lang/fr.ts +476 -27
  281. package/src/lang/ja.ts +465 -27
  282. package/src/lang/ko.ts +466 -27
  283. package/src/lang/pt-BR.ts +473 -27
  284. package/src/lang/zh.ts +463 -27
  285. package/src/lib/vue-i18n.ts +1 -1
  286. package/src/lib/wiki-page/slug.ts +66 -0
  287. package/src/main.ts +85 -0
  288. package/src/plugins/_extras.ts +58 -0
  289. package/src/plugins/_generated/metas.ts +42 -0
  290. package/src/plugins/_generated/registrations.ts +44 -0
  291. package/src/plugins/_generated/server-bindings.ts +47 -0
  292. package/src/plugins/accounting/Preview.vue +106 -0
  293. package/src/plugins/accounting/View.vue +632 -0
  294. package/src/plugins/accounting/actions.ts +34 -0
  295. package/src/plugins/accounting/api.ts +301 -0
  296. package/src/plugins/accounting/components/AccountEditor.vue +250 -0
  297. package/src/plugins/accounting/components/AccountRow.vue +50 -0
  298. package/src/plugins/accounting/components/AccountsList.vue +102 -0
  299. package/src/plugins/accounting/components/AccountsModal.vue +300 -0
  300. package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
  301. package/src/plugins/accounting/components/BookSettings.vue +284 -0
  302. package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
  303. package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
  304. package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
  305. package/src/plugins/accounting/components/JournalList.vue +553 -0
  306. package/src/plugins/accounting/components/Ledger.vue +206 -0
  307. package/src/plugins/accounting/components/NewBookForm.vue +211 -0
  308. package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
  309. package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
  310. package/src/plugins/accounting/components/accountDraft.ts +13 -0
  311. package/src/plugins/accounting/components/accountNumbering.ts +103 -0
  312. package/src/plugins/accounting/components/accountValidation.ts +75 -0
  313. package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
  314. package/src/plugins/accounting/countries.ts +158 -0
  315. package/src/plugins/accounting/currencies.ts +64 -0
  316. package/src/plugins/accounting/dates.ts +51 -0
  317. package/src/plugins/accounting/definition.ts +199 -0
  318. package/src/plugins/accounting/fiscalYear.ts +136 -0
  319. package/src/plugins/accounting/index.ts +49 -0
  320. package/src/plugins/accounting/meta.ts +91 -0
  321. package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
  322. package/src/plugins/api.ts +125 -0
  323. package/src/plugins/canvas/View.vue +38 -28
  324. package/src/plugins/canvas/definition.ts +10 -8
  325. package/src/plugins/canvas/index.ts +15 -8
  326. package/src/plugins/canvas/meta.ts +12 -0
  327. package/src/plugins/chart/Preview.vue +1 -1
  328. package/src/plugins/chart/View.vue +2 -2
  329. package/src/plugins/chart/definition.ts +12 -2
  330. package/src/plugins/chart/index.ts +15 -7
  331. package/src/plugins/chart/meta.ts +18 -0
  332. package/src/plugins/editImages/definition.ts +44 -0
  333. package/src/plugins/editImages/index.ts +43 -0
  334. package/src/plugins/editImages/meta.ts +5 -0
  335. package/src/plugins/generateImage/View.vue +3 -1
  336. package/src/plugins/generateImage/definition.ts +2 -0
  337. package/src/plugins/generateImage/index.ts +13 -5
  338. package/src/plugins/generateImage/meta.ts +5 -0
  339. package/src/plugins/index.ts +35 -0
  340. package/src/plugins/manageRoles/Preview.vue +7 -4
  341. package/src/plugins/manageRoles/View.vue +12 -8
  342. package/src/plugins/manageRoles/definition.ts +6 -0
  343. package/src/plugins/manageRoles/index.ts +7 -6
  344. package/src/plugins/manageSkills/View.vue +11 -7
  345. package/src/plugins/manageSkills/definition.ts +4 -1
  346. package/src/plugins/manageSkills/index.ts +14 -7
  347. package/src/plugins/manageSkills/meta.ts +21 -0
  348. package/src/plugins/manageSource/definition.ts +4 -1
  349. package/src/plugins/manageSource/index.ts +15 -7
  350. package/src/plugins/manageSource/meta.ts +21 -0
  351. package/src/plugins/markdown/Preview.vue +10 -8
  352. package/src/plugins/markdown/View.vue +84 -17
  353. package/src/plugins/markdown/definition.ts +7 -1
  354. package/src/plugins/markdown/index.ts +15 -8
  355. package/src/plugins/markdown/meta.ts +16 -0
  356. package/src/plugins/meta-types.ts +97 -0
  357. package/src/plugins/metas.ts +224 -0
  358. package/src/plugins/presentForm/Preview.vue +4 -15
  359. package/src/plugins/presentForm/View.vue +35 -78
  360. package/src/plugins/presentForm/definition.ts +7 -6
  361. package/src/plugins/presentForm/index.ts +12 -5
  362. package/src/plugins/presentForm/meta.ts +11 -0
  363. package/src/plugins/presentForm/plugin.ts +8 -9
  364. package/src/plugins/presentForm/types.ts +0 -24
  365. package/src/plugins/presentHtml/Preview.vue +1 -8
  366. package/src/plugins/presentHtml/View.vue +401 -30
  367. package/src/plugins/presentHtml/definition.ts +8 -5
  368. package/src/plugins/presentHtml/index.ts +15 -8
  369. package/src/plugins/presentHtml/meta.ts +14 -0
  370. package/src/plugins/presentMulmoScript/View.vue +327 -107
  371. package/src/plugins/presentMulmoScript/definition.ts +34 -7
  372. package/src/plugins/presentMulmoScript/helpers.ts +4 -5
  373. package/src/plugins/presentMulmoScript/index.ts +20 -7
  374. package/src/plugins/presentMulmoScript/meta.ts +52 -0
  375. package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
  376. package/src/plugins/scheduler/Preview.vue +5 -2
  377. package/src/plugins/scheduler/TasksTab.vue +16 -36
  378. package/src/plugins/scheduler/View.vue +22 -54
  379. package/src/plugins/scheduler/automationsDefinition.ts +14 -9
  380. package/src/plugins/scheduler/automationsMeta.ts +5 -0
  381. package/src/plugins/scheduler/calendarDefinition.ts +4 -7
  382. package/src/plugins/scheduler/calendarMeta.ts +28 -0
  383. package/src/plugins/scheduler/formatSchedule.ts +6 -24
  384. package/src/plugins/scheduler/index.ts +26 -52
  385. package/src/plugins/scope.ts +57 -0
  386. package/src/plugins/server-bindings-types.ts +38 -0
  387. package/src/plugins/server.ts +32 -0
  388. package/src/plugins/skill/Preview.vue +25 -0
  389. package/src/plugins/skill/View.vue +125 -0
  390. package/src/plugins/skill/definition.ts +23 -0
  391. package/src/plugins/skill/index.ts +36 -0
  392. package/src/plugins/skill/plugin.ts +31 -0
  393. package/src/plugins/skill/types.ts +21 -0
  394. package/src/plugins/spreadsheet/Preview.vue +1 -3
  395. package/src/plugins/spreadsheet/View.vue +29 -49
  396. package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
  397. package/src/plugins/spreadsheet/definition.ts +5 -2
  398. package/src/plugins/spreadsheet/index.ts +15 -8
  399. package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
  400. package/src/plugins/spreadsheet/meta.ts +14 -0
  401. package/src/plugins/textResponse/Preview.vue +9 -1
  402. package/src/plugins/textResponse/View.vue +59 -8
  403. package/src/plugins/textResponse/index.ts +11 -3
  404. package/src/plugins/textResponse/plugin.ts +8 -10
  405. package/src/plugins/textResponse/types.ts +28 -0
  406. package/src/plugins/wiki/Preview.vue +6 -4
  407. package/src/plugins/wiki/View.vue +463 -254
  408. package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
  409. package/src/plugins/wiki/helpers.ts +17 -0
  410. package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
  411. package/src/plugins/wiki/history/HistoryTab.vue +167 -0
  412. package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
  413. package/src/plugins/wiki/history/api.ts +52 -0
  414. package/src/plugins/wiki/history/diff.ts +145 -0
  415. package/src/plugins/wiki/index.ts +42 -32
  416. package/src/plugins/wiki/meta.ts +10 -0
  417. package/src/plugins/wiki/pageEditLoader.ts +53 -0
  418. package/src/plugins/wiki/route.ts +8 -0
  419. package/src/router/guards.ts +2 -1
  420. package/src/router/index.ts +19 -0
  421. package/src/router/pageRoutes.ts +1 -0
  422. package/src/tools/index.ts +50 -51
  423. package/src/tools/runtimeLoader.ts +141 -0
  424. package/src/tools/types.ts +44 -1
  425. package/src/types/notification.ts +23 -0
  426. package/src/types/pastedFile.ts +10 -0
  427. package/src/types/session.ts +61 -3
  428. package/src/types/sse.ts +21 -6
  429. package/src/utils/agent/eventDispatch.ts +12 -9
  430. package/src/utils/agent/pastedAttachment.ts +35 -0
  431. package/src/utils/agent/request.ts +32 -3
  432. package/src/utils/agent/toolCalls.ts +7 -1
  433. package/src/utils/api.ts +1 -1
  434. package/src/utils/chat/exportMarkdown.ts +243 -0
  435. package/src/utils/errors.ts +10 -2
  436. package/src/utils/files/expandedDirs.ts +1 -1
  437. package/src/utils/filesPreview/todoPreview.ts +13 -2
  438. package/src/utils/format/date.ts +1 -3
  439. package/src/utils/format/jsonSyntax.ts +5 -0
  440. package/src/utils/html/iframeHeightReporterScript.ts +62 -0
  441. package/src/utils/html/previewCsp.ts +29 -2
  442. package/src/utils/image/htmlSrcAttrs.ts +122 -0
  443. package/src/utils/image/imageRepairInlineScript.ts +115 -0
  444. package/src/utils/image/resolve.ts +17 -3
  445. package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
  446. package/src/utils/markdown/frontmatter.ts +125 -0
  447. package/src/utils/markdown/taskList.ts +7 -2
  448. package/src/utils/plugin/runtime.ts +132 -0
  449. package/src/utils/session/mergeSessions.ts +40 -37
  450. package/src/utils/session/sessionEntries.ts +74 -18
  451. package/src/utils/session/sessionHelpers.ts +54 -10
  452. package/src/utils/tools/result.ts +76 -14
  453. package/src/vite-env.d.ts +6 -0
  454. package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
  455. package/client/assets/index-CY-WpQUm.css +0 -2
  456. package/client/assets/index-DbTz2Mfs.js +0 -4911
  457. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  458. package/server/api/routes/html.ts +0 -114
  459. package/server/api/routes/todos.ts +0 -293
  460. package/server/api/routes/todosColumnsHandlers.ts +0 -333
  461. package/server/api/routes/todosHandlers.ts +0 -274
  462. package/server/api/routes/todosItemsHandlers.ts +0 -386
  463. package/server/utils/files/todos-io.ts +0 -29
  464. package/src/components/NotificationToast.vue +0 -75
  465. package/src/plugins/editImage/definition.ts +0 -27
  466. package/src/plugins/editImage/index.ts +0 -37
  467. package/src/plugins/presentHtml/helpers.ts +0 -72
  468. package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
  469. package/src/plugins/scheduler/legacyShape.ts +0 -34
  470. package/src/plugins/todo/Preview.vue +0 -68
  471. package/src/plugins/todo/View.vue +0 -378
  472. package/src/plugins/todo/composables/useTodos.ts +0 -179
  473. package/src/plugins/todo/definition.ts +0 -45
  474. package/src/plugins/todo/index.ts +0 -62
  475. package/src/plugins/todo/labels.ts +0 -163
  476. package/src/plugins/todo/priority.ts +0 -98
  477. package/src/plugins/todo/viewModes.ts +0 -19
  478. package/src/plugins/wiki/definition.ts +0 -25
  479. package/src/tools/legacyPluginNames.ts +0 -13
  480. package/src/utils/format/frontmatter.ts +0 -80
  481. package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
  482. package/src/utils/notification/dispatch.ts +0 -58
  483. /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
  484. /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
  485. /package/src/plugins/{editImage → editImages}/View.vue +0 -0
  486. /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
@@ -0,0 +1,142 @@
1
+ // Topic-based memory schema (#1070). Each file groups related
2
+ // bullets under a single topic. The frontmatter carries the type
3
+ // and topic name; the body is markdown (H1 + optional H2 sections +
4
+ // bullets). Coexists with #1029's atomic files during the
5
+ // transition — PR-B retires the atomic format.
6
+
7
+ import type { MemoryType } from "./types.js";
8
+
9
+ export interface TopicMemoryFile {
10
+ type: MemoryType;
11
+ /** Filename without `.md`. Stable identifier; the index links to it. */
12
+ topic: string;
13
+ /** Raw markdown body — H1 heading + H2 subsections + bullets. */
14
+ body: string;
15
+ /** H2 headings extracted from the body, in source order, with
16
+ * whitespace trimmed. The index renders these as the file's
17
+ * "tags". Empty array when the file has no H2 yet (a young
18
+ * topic with only bullets directly under H1). */
19
+ sections: string[];
20
+ }
21
+
22
+ // Pull H2 headings out of the body. Pure / regex-free / iterable so
23
+ // a long body can't trip sonarjs. `##` followed by space OR tab is
24
+ // accepted so a markdown file authored with mixed indentation
25
+ // doesn't silently lose tags. `###` and deeper are NOT treated as
26
+ // tags so the file can use them for further structure without
27
+ // leaking into the index.
28
+ export function extractH2Sections(body: string): string[] {
29
+ const sections: string[] = [];
30
+ for (const lineRaw of body.split("\n")) {
31
+ const line = stripCarriageReturn(lineRaw);
32
+ if (!isH2Line(line)) continue;
33
+ const heading = line.slice(2).trim();
34
+ if (heading.length > 0) sections.push(heading);
35
+ }
36
+ return sections;
37
+ }
38
+
39
+ function isH2Line(line: string): boolean {
40
+ if (!line.startsWith("##")) return false;
41
+ if (line.startsWith("###")) return false;
42
+ if (line.length === 2) return false;
43
+ const [, , next] = line;
44
+ return next === " " || next === "\t";
45
+ }
46
+
47
+ function stripCarriageReturn(line: string): string {
48
+ return line.endsWith("\r") ? line.slice(0, -1) : line;
49
+ }
50
+
51
+ // Maximum length of a topic slug (filename without `.md`). Both the
52
+ // slugifier and the safety gate honour this cap, and downstream
53
+ // callers that suffix a slug (e.g. collision resolution in
54
+ // `topic-migrate`'s `pickUniqueSlug`) must trim the base so the
55
+ // suffixed result still fits.
56
+ export const MAX_TOPIC_SLUG_LENGTH = 60;
57
+
58
+ // Windows refuses to create files with these basenames even with an
59
+ // extension (`con.md` is blocked). Without this gate a clusterer
60
+ // that returns "CON" would slugify to `con`, pass `isSafeTopicSlug`,
61
+ // and then fail migration on Windows. Listed in lowercase since the
62
+ // slugifier lowercases first.
63
+ const WINDOWS_RESERVED_BASENAMES = new Set([
64
+ "con",
65
+ "prn",
66
+ "aux",
67
+ "nul",
68
+ "com1",
69
+ "com2",
70
+ "com3",
71
+ "com4",
72
+ "com5",
73
+ "com6",
74
+ "com7",
75
+ "com8",
76
+ "com9",
77
+ "lpt1",
78
+ "lpt2",
79
+ "lpt3",
80
+ "lpt4",
81
+ "lpt5",
82
+ "lpt6",
83
+ "lpt7",
84
+ "lpt8",
85
+ "lpt9",
86
+ ]);
87
+
88
+ // Slugify a topic name for use as a filename. `<type>/<topic>.md`
89
+ // must keep `topic` filesystem-safe and short. Collapses anything
90
+ // non-alnum into a single `-`, lowercases, trims trailing
91
+ // separators, caps at MAX_TOPIC_SLUG_LENGTH chars. Returns null when
92
+ // the result is empty (caller decides whether to fall back to a
93
+ // hash).
94
+ export function slugifyTopicName(name: string): string | null {
95
+ const lower = name.toLowerCase();
96
+ const out: string[] = [];
97
+ let lastWasSep = true;
98
+ for (const char of lower) {
99
+ if ((char >= "a" && char <= "z") || (char >= "0" && char <= "9")) {
100
+ out.push(char);
101
+ lastWasSep = false;
102
+ } else if (!lastWasSep) {
103
+ out.push("-");
104
+ lastWasSep = true;
105
+ }
106
+ }
107
+ while (out.length > 0 && out[out.length - 1] === "-") out.pop();
108
+ const compact = out.slice(0, MAX_TOPIC_SLUG_LENGTH).join("");
109
+ if (compact.length === 0) return null;
110
+ const trimmed = trimTrailing(compact, "-");
111
+ if (WINDOWS_RESERVED_BASENAMES.has(trimmed)) return null;
112
+ return trimmed;
113
+ }
114
+
115
+ function trimTrailing(text: string, char: string): string {
116
+ let end = text.length;
117
+ while (end > 0 && text[end - 1] === char) end -= 1;
118
+ return text.slice(0, end);
119
+ }
120
+
121
+ // Strict shape gate: lowercase alnum + `-` only, length 1–60, no
122
+ // leading / trailing `-`, not the reserved index name. The
123
+ // strictness is intentional: a clusterer's first attempt at a topic
124
+ // slug is often a free-form phrase ("AI Research Papers!"), and we
125
+ // want such inputs to fall through to `slugifyTopicName` rather
126
+ // than land verbatim on the filesystem with spaces / punctuation
127
+ // baked in. `isSafeMemorySlug` from #1029 was looser to allow
128
+ // unicode body suffixes; here we force the slug format to be
129
+ // filename-friendly so the topic doubles as the path component.
130
+ export function isSafeTopicSlug(slug: string): boolean {
131
+ if (typeof slug !== "string") return false;
132
+ if (slug.length === 0) return false;
133
+ if (slug.length > MAX_TOPIC_SLUG_LENGTH) return false;
134
+ if (slug.startsWith("-") || slug.endsWith("-")) return false;
135
+ for (const char of slug) {
136
+ const ok = (char >= "a" && char <= "z") || (char >= "0" && char <= "9") || char === "-";
137
+ if (!ok) return false;
138
+ }
139
+ if (slug === "memory") return false;
140
+ if (WINDOWS_RESERVED_BASENAMES.has(slug)) return false;
141
+ return true;
142
+ }
@@ -0,0 +1,83 @@
1
+ // Typed memory schema (#1029). Each entry is a markdown file with a
2
+ // YAML frontmatter envelope. The directory layout, filename
3
+ // convention, and field set are documented in
4
+ // `plans/done/feat-memory-storage-utilities.md`.
5
+ //
6
+ // `type` is the source of truth for an entry's classification —
7
+ // filenames follow `<type>_<slug>.md` for ergonomics but the reader
8
+ // must trust the frontmatter, never the filename.
9
+
10
+ export const MEMORY_TYPES = ["preference", "interest", "fact", "reference"] as const;
11
+
12
+ export type MemoryType = (typeof MEMORY_TYPES)[number];
13
+
14
+ export interface MemoryEntry {
15
+ /** One-line human-readable label. Becomes the link text in MEMORY.md. */
16
+ name: string;
17
+ /** Short blurb shown after the link in MEMORY.md and used as the
18
+ * description hint when the agent decides whether to read the
19
+ * full entry. */
20
+ description: string;
21
+ type: MemoryType;
22
+ /** Markdown body. The frontmatter envelope is stripped on parse and
23
+ * re-applied on write. */
24
+ body: string;
25
+ /** Filename without extension. Stable identifier used by the index
26
+ * link target. */
27
+ slug: string;
28
+ }
29
+
30
+ export function isMemoryType(value: unknown): value is MemoryType {
31
+ return typeof value === "string" && (MEMORY_TYPES as readonly string[]).includes(value);
32
+ }
33
+
34
+ // Bound the alphanumeric portion of a slug. Long bullets in the
35
+ // legacy memory.md (e.g. paragraph-length recurring task descriptions)
36
+ // would otherwise produce slugs > 200 chars and trip
37
+ // `isSafeMemorySlug`'s upper bound, dropping the entry on disk. 80
38
+ // chars keeps the filename readable in `ls` output, leaves headroom
39
+ // under the safety cap, and is well under the 255-byte filename limit
40
+ // every supported filesystem honors.
41
+ const MAX_SLUG_BODY = 80;
42
+
43
+ // Slugify a name into a filename-safe token. ASCII-only conversion of
44
+ // [a-zA-Z0-9] segments; everything else collapses into a single `-`.
45
+ // The result is truncated to MAX_SLUG_BODY and any trailing `-` from
46
+ // the truncation is trimmed so the slug doesn't read like a partial
47
+ // word. Non-ASCII (Japanese / 中文) input falls back to the type
48
+ // prefix + short hash so two entries with all-non-ASCII names don't
49
+ // collide on the empty string. Uses an explicit char loop instead of
50
+ // regex so a deeply-recursive name can't trigger pathological
51
+ // backtracking.
52
+ export function slugifyMemoryName(name: string, type: MemoryType): string {
53
+ const ascii = compactAlnum(name.toLowerCase());
54
+ const bounded = trimTrailingSeparators(ascii.slice(0, MAX_SLUG_BODY));
55
+ if (bounded.length > 0) return `${type}_${bounded}`;
56
+ let hash = 0;
57
+ for (let index = 0; index < name.length; index += 1) {
58
+ hash = (hash * 31 + name.charCodeAt(index)) >>> 0;
59
+ }
60
+ return `${type}_${hash.toString(36)}`;
61
+ }
62
+
63
+ function trimTrailingSeparators(text: string): string {
64
+ let end = text.length;
65
+ while (end > 0 && text[end - 1] === "-") end -= 1;
66
+ return text.slice(0, end);
67
+ }
68
+
69
+ function compactAlnum(text: string): string {
70
+ const out: string[] = [];
71
+ let lastWasSep = true;
72
+ for (const char of text) {
73
+ if ((char >= "a" && char <= "z") || (char >= "0" && char <= "9")) {
74
+ out.push(char);
75
+ lastWasSep = false;
76
+ } else if (!lastWasSep) {
77
+ out.push("-");
78
+ lastWasSep = true;
79
+ }
80
+ }
81
+ while (out.length > 0 && out[out.length - 1] === "-") out.pop();
82
+ return out.join("");
83
+ }
@@ -83,8 +83,8 @@ export function extractDailyJsonIndex(markdown: string): NewsItem[] | null {
83
83
  if (!Array.isArray(shape.items)) return null;
84
84
  // Defensive filter: drop entries that don't carry the fields the
85
85
  // viewer relies on.
86
- return shape.items.filter((item): item is NewsItem => {
87
- return (
86
+ return shape.items.filter(
87
+ (item): item is NewsItem =>
88
88
  typeof item === "object" &&
89
89
  item !== null &&
90
90
  typeof item.id === "string" &&
@@ -92,9 +92,8 @@ export function extractDailyJsonIndex(markdown: string): NewsItem[] | null {
92
92
  typeof item.url === "string" &&
93
93
  typeof item.publishedAt === "string" &&
94
94
  typeof item.sourceSlug === "string" &&
95
- Array.isArray(item.categories)
96
- );
97
- });
95
+ Array.isArray(item.categories),
96
+ );
98
97
  }
99
98
 
100
99
  // Build the list of YYYY-MM-DD strings for the last `days` days
@@ -24,6 +24,48 @@
24
24
  import { homedir } from "os";
25
25
  import path from "path";
26
26
 
27
+ // Well-known individual files — imported from the shared
28
+ // src/config/workspacePaths.ts (single source of truth for both
29
+ // server and frontend). Re-exported so server callers keep the
30
+ // same `import { WORKSPACE_FILES } from "./paths.js"` they use.
31
+ import { WORKSPACE_FILES } from "../../src/config/workspacePaths.js";
32
+
33
+ // Plugin-owned workspace dirs are auto-aggregated from every
34
+ // plugin's META in `src/plugins/metas.ts`. Adding a new plugin =
35
+ // register its META there; this file keeps the central
36
+ // `WORKSPACE_DIRS.<key>` shape via spread so existing consumers
37
+ // don't migrate. Plugin-specific literals never appear here.
38
+ import {
39
+ BUILT_IN_PLUGIN_METAS,
40
+ defineHostAggregate,
41
+ type BuiltInPluginMetas,
42
+ type HostPluginCollision,
43
+ type IntraPluginCollision,
44
+ } from "../../src/plugins/metas.js";
45
+
46
+ // Merge every plugin's `workspaceDirs` into one record. The mapped
47
+ // type below preserves each plugin's literal path strings (e.g.
48
+ // `"data/accounting"`) so consumers like `WORKSPACE_DIRS.accounting`
49
+ // keep their narrow types — without it, TypeScript widens to
50
+ // `string` and downstream `WORKSPACE_PATHS.accounting` lookups break.
51
+ //
52
+ // Distributive conditional types collapse the per-plugin union into
53
+ // an INTERSECTION so consumers see the merged shape rather than a
54
+ // union (which TS won't let you index into safely once 2+ plugins
55
+ // register).
56
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
57
+
58
+ // Plugins WITHOUT `workspaceDirs` must contribute an empty object
59
+ // (`Record<never, never>`), NOT `Record<string, never>`. The latter
60
+ // carries an index signature that, once intersected with a sibling
61
+ // plugin's narrow contribution, drags `keyof` of the merged type
62
+ // back to `string`. See server/workspace/workspace.ts: indexing
63
+ // `WORKSPACE_PATHS[key]` over `EAGER_WORKSPACE_DIRS` requires
64
+ // `WorkspaceDirKey` to stay a union of literals.
65
+ type PluginWorkspaceDirsContribution<M> = M extends { readonly workspaceDirs: infer D } ? { readonly [K in keyof D]: D[K] } : Record<never, never>;
66
+
67
+ type PluginWorkspaceDirsMap<T extends BuiltInPluginMetas> = UnionToIntersection<PluginWorkspaceDirsContribution<T[number]>>;
68
+
27
69
  // Workspace root. Hard-coded to `~/mulmoclaude` — there is no
28
70
  // WORKSPACE_PATH env override today; changing the location
29
71
  // requires a code edit or a symlink. Re-exported by
@@ -34,9 +76,20 @@ export const workspacePath = path.join(homedir(), "mulmoclaude");
34
76
  // Workspace-relative paths. Keys are the stable code-side identifiers
35
77
  // (e.g. `markdowns` — unchanged for call-site compatibility); values
36
78
  // are the on-disk paths, grouped per issue #284.
37
- export const WORKSPACE_DIRS = {
79
+ const HOST_WORKSPACE_DIRS = {
38
80
  // conversations/
39
81
  chat: "conversations/chat",
82
+ // Typed memory entries (#1029). One markdown file per fact, indexed
83
+ // by `MEMORY.md` (= WORKSPACE_FILES.memoryIndex). Replaces the
84
+ // single-file `memory.md`; the legacy file is kept as
85
+ // `memory.md.backup` after migration.
86
+ memoryDir: "conversations/memory",
87
+ // Staging dir for the atomic→topic migration (#1070 PR-A). Cluster
88
+ // output lands here; the user reviews via `diff`, then `topic-swap`
89
+ // promotes it to `memoryDir`. The dir name is also matched verbatim
90
+ // by `topicStagingPath` and the swap-window detection in
91
+ // `topic-detect.ts`, so changes here ripple through both places.
92
+ memoryStaging: "conversations/memory.next",
40
93
  summaries: "conversations/summaries",
41
94
  // Tool-trace output for WebSearch (one .md per search, referenced
42
95
  // from chat JSONL `contentRef`). Lives alongside chat/ so search
@@ -44,11 +97,18 @@ export const WORKSPACE_DIRS = {
44
97
  searches: "conversations/searches",
45
98
  // data/
46
99
  wiki: "data/wiki",
47
- todos: "data/todos",
100
+ // todos: removed (#1145) — todo data now lives under the plugin's
101
+ // own `files.data` scope at `data/plugins/%40mulmoclaude%2Ftodo-plugin/`.
48
102
  calendar: "data/calendar",
49
103
  contacts: "data/contacts",
50
104
  scheduler: "data/scheduler",
51
105
  sources: "data/sources",
106
+ translation: "data/translation",
107
+ // Pasted/dropped chat attachments — saved at upload time so the
108
+ // LLM can be handed a stable workspace path instead of inline
109
+ // base64. Conversion artefacts (e.g. PPTX → PDF) live alongside
110
+ // the original under the same YYYY/MM partition.
111
+ attachments: "data/attachments",
52
112
  transports: "data/transports",
53
113
  // artifacts/
54
114
  charts: "artifacts/charts",
@@ -71,6 +131,13 @@ export const WORKSPACE_DIRS = {
71
131
  configs: "config",
72
132
  roles: "config/roles",
73
133
  helps: "config/helps",
134
+ // Project-scope Claude Code skills root — both user-authored and
135
+ // launcher-managed presets live here. Path is hardcoded by Claude
136
+ // Code's slash-command resolver (it scans `<cwd>/.claude/skills/`
137
+ // alongside `~/.claude/skills/`); we centralise the literal here
138
+ // so server code references it through `WORKSPACE_PATHS.claudeSkills`
139
+ // instead of inlining the string.
140
+ claudeSkills: ".claude/skills",
74
141
  // Nested subdirs inside a top-level grouping. Kept here (rather
75
142
  // than module-local constants) when multiple modules need to
76
143
  // reference the same nested path — e.g. wiki/pages/ is used by
@@ -78,61 +145,72 @@ export const WORKSPACE_DIRS = {
78
145
  // prompt hint.
79
146
  wikiPages: "data/wiki/pages",
80
147
  wikiSources: "data/wiki/sources",
148
+ // Per-page edit-history snapshots (#763 PR 2). Hidden by leading
149
+ // dot so a curious user listing `data/wiki/` doesn't trip over a
150
+ // peer directory of historical content. Each `<slug>/` underneath
151
+ // holds N snapshot .md files newest-first.
152
+ wikiHistory: "data/wiki/.history",
81
153
  // Development — git-cloned repositories (#256).
82
154
  github: "github",
155
+ // Runtime-loaded plugins (#1043 C-2). The `plugins/` directory holds
156
+ // user-installed npm-published plugin tarballs; `.cache/<name>/<ver>/`
157
+ // is the extracted-on-boot mirror. Both live under the workspace root
158
+ // so the install / extract artefacts persist across npx invocations.
159
+ plugins: "plugins",
160
+ pluginCache: "plugins/.cache",
161
+ // Per-runtime-plugin storage roots (#1110). The platform creates
162
+ // `<root>/<sanitized-pkg-name>/` lazily on first write. data is the
163
+ // backup target; config holds per-machine UI state / defaults.
164
+ pluginsData: "data/plugins",
165
+ pluginsConfig: "config/plugins",
166
+ notifier: "data/notifier",
83
167
  } as const;
84
168
 
85
- // Well-known individual files imported from the shared
86
- // src/config/workspacePaths.ts (single source of truth for both
87
- // server and frontend). Re-exported so server callers keep the
88
- // same `import { WORKSPACE_FILES } from "./paths.js"` they use.
89
- import { WORKSPACE_FILES } from "../../src/config/workspacePaths.js";
169
+ // First-write-wins host+plugin aggregate (see `defineHostAggregate`):
170
+ // host keys win on collision, second-claiming plugin wins are
171
+ // dropped, both diagnostic lists are exposed for boot warnings.
172
+ const WORKSPACE_DIRS_AGGREGATE = defineHostAggregate(BUILT_IN_PLUGIN_METAS, {
173
+ label: "WORKSPACE_DIRS",
174
+ hostRecord: HOST_WORKSPACE_DIRS,
175
+ // Reserve `WORKSPACE_FILES` keys too — those land in `WORKSPACE_PATHS`
176
+ // alongside dir paths and would silently overwrite a plugin's
177
+ // `workspaceDirs.<sameKey>` at the absolute-path step, leaving
178
+ // `WORKSPACE_DIRS` and `WORKSPACE_PATHS` disagreeing for that key
179
+ // (CR review #1125).
180
+ additionalReservedKeys: new Set(Object.keys(WORKSPACE_FILES)),
181
+ extract: (meta) => meta.workspaceDirs,
182
+ dimension: "workspaceDirs",
183
+ });
184
+ export const WORKSPACE_DIRS_HOST_COLLISIONS: readonly HostPluginCollision[] = WORKSPACE_DIRS_AGGREGATE.hostCollisions;
185
+ export const WORKSPACE_DIRS_INTRA_COLLISIONS: readonly IntraPluginCollision[] = WORKSPACE_DIRS_AGGREGATE.intraCollisions;
186
+
187
+ export const WORKSPACE_DIRS = WORKSPACE_DIRS_AGGREGATE.merged as unknown as typeof HOST_WORKSPACE_DIRS & PluginWorkspaceDirsMap<BuiltInPluginMetas>;
90
188
  export { WORKSPACE_FILES };
91
189
 
92
190
  // Absolute paths, built once at module load from `workspacePath`.
93
191
  // The `workspacePath` const is itself fixed (reads `homedir()`
94
192
  // at process start — no env override, see `server/workspace.ts`),
95
193
  // so freezing these paths is safe.
194
+ //
195
+ // Auto-derived from `WORKSPACE_DIRS` and `WORKSPACE_FILES`. Adding
196
+ // a new dir or file to the upstream maps now flows into
197
+ // `WORKSPACE_PATHS` automatically — no second hand-curated edit
198
+ // required (CodeRabbit #1125 review: previously plugins adding
199
+ // `workspaceDirs` keys still needed a manual `WORKSPACE_PATHS`
200
+ // patch-up to be reachable in absolute form).
201
+ const WORKSPACE_DIR_PATHS = Object.fromEntries(Object.entries(WORKSPACE_DIRS).map(([key, relativePath]) => [key, path.join(workspacePath, relativePath)])) as {
202
+ readonly [K in keyof typeof WORKSPACE_DIRS]: string;
203
+ };
204
+
205
+ const WORKSPACE_FILE_PATHS = Object.fromEntries(
206
+ Object.entries(WORKSPACE_FILES).map(([key, relativePath]) => [key, path.join(workspacePath, relativePath)]),
207
+ ) as {
208
+ readonly [K in keyof typeof WORKSPACE_FILES]: string;
209
+ };
210
+
96
211
  export const WORKSPACE_PATHS = {
97
- chat: path.join(workspacePath, WORKSPACE_DIRS.chat),
98
- todos: path.join(workspacePath, WORKSPACE_DIRS.todos),
99
- calendar: path.join(workspacePath, WORKSPACE_DIRS.calendar),
100
- contacts: path.join(workspacePath, WORKSPACE_DIRS.contacts),
101
- scheduler: path.join(workspacePath, WORKSPACE_DIRS.scheduler),
102
- roles: path.join(workspacePath, WORKSPACE_DIRS.roles),
103
- stories: path.join(workspacePath, WORKSPACE_DIRS.stories),
104
- images: path.join(workspacePath, WORKSPACE_DIRS.images),
105
- markdowns: path.join(workspacePath, WORKSPACE_DIRS.markdowns),
106
- spreadsheets: path.join(workspacePath, WORKSPACE_DIRS.spreadsheets),
107
- charts: path.join(workspacePath, WORKSPACE_DIRS.charts),
108
- configs: path.join(workspacePath, WORKSPACE_DIRS.configs),
109
- helps: path.join(workspacePath, WORKSPACE_DIRS.helps),
110
- wiki: path.join(workspacePath, WORKSPACE_DIRS.wiki),
111
- news: path.join(workspacePath, WORKSPACE_DIRS.news),
112
- sources: path.join(workspacePath, WORKSPACE_DIRS.sources),
113
- summaries: path.join(workspacePath, WORKSPACE_DIRS.summaries),
114
- searches: path.join(workspacePath, WORKSPACE_DIRS.searches),
115
- htmls: path.join(workspacePath, WORKSPACE_DIRS.htmls),
116
- html: path.join(workspacePath, WORKSPACE_DIRS.html),
117
- transports: path.join(workspacePath, WORKSPACE_DIRS.transports),
118
- github: path.join(workspacePath, WORKSPACE_DIRS.github),
119
- // nested subdirs
120
- wikiPages: path.join(workspacePath, WORKSPACE_DIRS.wikiPages),
121
- wikiSources: path.join(workspacePath, WORKSPACE_DIRS.wikiSources),
122
- // files
123
- memory: path.join(workspacePath, WORKSPACE_FILES.memory),
124
- sessionToken: path.join(workspacePath, WORKSPACE_FILES.sessionToken),
125
- wikiIndex: path.join(workspacePath, WORKSPACE_FILES.wikiIndex),
126
- wikiLog: path.join(workspacePath, WORKSPACE_FILES.wikiLog),
127
- wikiSchema: path.join(workspacePath, WORKSPACE_FILES.wikiSchema),
128
- wikiSummary: path.join(workspacePath, WORKSPACE_FILES.wikiSummary),
129
- summariesIndex: path.join(workspacePath, WORKSPACE_FILES.summariesIndex),
130
- todosItems: path.join(workspacePath, WORKSPACE_FILES.todosItems),
131
- todosColumns: path.join(workspacePath, WORKSPACE_FILES.todosColumns),
132
- schedulerItems: path.join(workspacePath, WORKSPACE_FILES.schedulerItems),
133
- schedulerUserTasks: path.join(workspacePath, WORKSPACE_FILES.schedulerUserTasks),
134
- schedulerOverrides: path.join(workspacePath, WORKSPACE_FILES.schedulerOverrides),
135
- newsReadState: path.join(workspacePath, WORKSPACE_FILES.newsReadState),
212
+ ...WORKSPACE_DIR_PATHS,
213
+ ...WORKSPACE_FILE_PATHS,
136
214
  } as const;
137
215
 
138
216
  export type WorkspaceDirKey = keyof typeof WORKSPACE_DIRS;
@@ -144,7 +222,6 @@ export type WorkspacePathKey = keyof typeof WORKSPACE_PATHS;
144
222
  // list is created lazily (first write) by its owning module.
145
223
  export const EAGER_WORKSPACE_DIRS: readonly WorkspaceDirKey[] = [
146
224
  "chat",
147
- "todos",
148
225
  "calendar",
149
226
  "contacts",
150
227
  "scheduler",
@@ -1,16 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { BUILTIN_ROLES, RoleSchema, type Role } from "../../src/config/roles.js";
3
- import { WORKSPACE_DIRS } from "./paths.js";
3
+ import { WORKSPACE_DIRS, workspacePath } from "./paths.js";
4
4
  import { readdirUnderSync, readTextUnderSync } from "../utils/files/workspace-io.js";
5
- import { workspacePath } from "./paths.js";
6
-
7
- function withSwitchRole(role: Role): Role {
8
- if (role.availablePlugins.includes("switchRole")) return role;
9
- return {
10
- ...role,
11
- availablePlugins: [...role.availablePlugins, "switchRole"],
12
- };
13
- }
14
5
 
15
6
  export function loadCustomRoles(): Role[] {
16
7
  return readdirUnderSync(workspacePath, WORKSPACE_DIRS.roles)
@@ -19,7 +10,7 @@ export function loadCustomRoles(): Role[] {
19
10
  try {
20
11
  const raw = readTextUnderSync(workspacePath, path.posix.join(WORKSPACE_DIRS.roles, fileName));
21
12
  if (!raw) return [];
22
- return [withSwitchRole(RoleSchema.parse(JSON.parse(raw)))];
13
+ return [RoleSchema.parse(JSON.parse(raw))];
23
14
  } catch {
24
15
  return [];
25
16
  }
@@ -6,6 +6,8 @@
6
6
  // pulling in a YAML parser we do line-by-line extraction.
7
7
 
8
8
  import { TIME_UNIT_MS, ONE_SECOND_MS } from "../../utils/time.js";
9
+ import { LEADING_BLANK_LINES_PATTERN } from "../../utils/regex.js";
10
+ import { parseFrontmatter } from "../../utils/markdown/frontmatter.js";
9
11
  import { SCHEDULE_TYPES } from "@receptron/task-scheduler";
10
12
 
11
13
  export interface SkillSchedule {
@@ -24,21 +26,6 @@ export interface ParsedSkill {
24
26
  roleId?: string;
25
27
  }
26
28
 
27
- // Match a YAML scalar value on a single line:
28
- // description: Enable CI for a repository
29
- // description: "Quoted with colons: inside"
30
- // Leading/trailing whitespace trimmed. Quoted values have their
31
- // outer quotes stripped but inner JSON-style escapes are NOT
32
- // reversed — SKILL.md descriptions in the wild are plain text.
33
- function parseScalar(raw: string): string {
34
- const trimmed = raw.trim();
35
- if (trimmed.length === 0) return "";
36
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
37
- return trimmed.slice(1, -1);
38
- }
39
- return trimmed;
40
- }
41
-
42
29
  /**
43
30
  * Parse schedule value from frontmatter.
44
31
  * Supported formats:
@@ -68,8 +55,8 @@ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
68
55
  // interval Ns / Nm / Nh — must be >= MIN_INTERVAL_MS
69
56
  const intervalMatch = trimmed.match(/^interval\s+(\d+)([smh])$/);
70
57
  if (intervalMatch) {
71
- const value = Number(intervalMatch[1]);
72
- const unit = intervalMatch[2];
58
+ const [, valueRaw, unit] = intervalMatch;
59
+ const value = Number(valueRaw);
73
60
  const unitMs = TIME_UNIT_MS[unit];
74
61
  if (!unitMs) return null;
75
62
  const intervalMs = value * unitMs;
@@ -80,6 +67,26 @@ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
80
67
  return null;
81
68
  }
82
69
 
70
+ /** Type guard — coerce the parsed-meta value at `key` into a
71
+ * string, mirroring the legacy `parseScalar` semantics so this
72
+ * refactor stays behaviour-neutral (codex review iter-1 #908):
73
+ *
74
+ * - key absent → null (legacy bailed out the same way)
75
+ * - string value → as-is, including the empty string
76
+ * - `null` value (from a bare `description:` line that js-yaml
77
+ * returns as `null`) → empty string, matching parseScalar's
78
+ * "" return for an empty raw value
79
+ * - structured / number → null (legacy didn't accept either —
80
+ * parseScalar's input is always a string slice)
81
+ */
82
+ function metaString(meta: Record<string, unknown>, key: string): string | null {
83
+ if (!(key in meta)) return null;
84
+ const value = meta[key];
85
+ if (typeof value === "string") return value;
86
+ if (value === null) return "";
87
+ return null;
88
+ }
89
+
83
90
  /**
84
91
  * Parse a SKILL.md file. Returns null when:
85
92
  * - the file has no frontmatter (no leading `---` fence)
@@ -87,50 +94,26 @@ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
87
94
  * - there is no `description:` key
88
95
  *
89
96
  * An empty body is allowed (the skill may be just metadata for now).
97
+ *
98
+ * Built on the shared `parseFrontmatter` helper (#895 PR C) so the
99
+ * envelope / scalar / quote handling matches the rest of the
100
+ * codebase. Only `description`, `schedule`, and `roleId` are
101
+ * extracted — extra keys in a SKILL.md file are silently ignored.
90
102
  */
91
- // Extract key-value pairs from YAML frontmatter lines. Returns a
92
- // map of key → scalar value. Keeps parseSkillFrontmatter under the
93
- // cognitive-complexity threshold.
94
- function extractFrontmatterFields(lines: string[], startIdx: number, endIdx: number): Map<string, string> {
95
- const fields = new Map<string, string>();
96
- for (let i = startIdx; i < endIdx; i++) {
97
- const line = lines[i];
98
- const colonIdx = line.indexOf(":");
99
- if (colonIdx === -1) continue;
100
- const key = line.slice(0, colonIdx).trim();
101
- const value = parseScalar(line.slice(colonIdx + 1));
102
- fields.set(key, value);
103
- }
104
- return fields;
105
- }
106
-
107
103
  export function parseSkillFrontmatter(raw: string): ParsedSkill | null {
108
- const lines = raw.split(/\r?\n/);
109
- if (lines.length === 0 || lines[0].trim() !== "---") return null;
110
-
111
- let closeIdx = -1;
112
- for (let i = 1; i < lines.length; i++) {
113
- if (lines[i].trim() === "---") {
114
- closeIdx = i;
115
- break;
116
- }
117
- }
118
- if (closeIdx === -1) return null;
104
+ const parsed = parseFrontmatter(raw);
105
+ if (!parsed.hasHeader) return null;
119
106
 
120
- const fields = extractFrontmatterFields(lines, 1, closeIdx);
121
- const description = fields.get("description") ?? null;
107
+ const description = metaString(parsed.meta, "description");
122
108
  if (description === null) return null;
123
109
 
124
- const scheduleRaw = fields.get("schedule") ?? null;
125
- const roleId = fields.get("roleId") ?? null;
110
+ const scheduleRaw = metaString(parsed.meta, "schedule");
111
+ const roleId = metaString(parsed.meta, "roleId");
126
112
 
127
- // Body starts after the closing fence. Trim leading blank lines so
128
- // the UI doesn't render an awkward gap above the first heading.
129
- const body = lines
130
- .slice(closeIdx + 1)
131
- .join("\n")
132
- .replace(/^(?:\s*\n)+/, "")
133
- .trimEnd();
113
+ // Trim leading blank lines so the UI doesn't render an awkward
114
+ // gap above the first heading. Pattern + ReDoS-safety rationale
115
+ // lives in `server/utils/regex.ts`.
116
+ const body = parsed.body.replace(LEADING_BLANK_LINES_PATTERN, "").trimEnd();
134
117
 
135
118
  const result: ParsedSkill = { description, body };
136
119
  if (scheduleRaw) {
@@ -10,11 +10,10 @@
10
10
  import { loadUserTasks as loadRaw, saveUserTasks } from "../../utils/files/user-tasks-io.js";
11
11
  import type { MissedRunPolicy } from "@receptron/task-scheduler";
12
12
  import { SCHEDULE_TYPES, MISSED_RUN_POLICIES } from "@receptron/task-scheduler";
13
- import type { TaskSchedule as LocalTaskSchedule } from "../../events/task-manager/index.js";
13
+ import type { TaskSchedule as LocalTaskSchedule, ITaskManager } from "../../events/task-manager/index.js";
14
14
  import { DEFAULT_ROLE_ID } from "../../../src/config/roles.js";
15
15
  import { SESSION_ORIGINS, type SessionOrigin } from "../../../src/types/session.js";
16
16
  import { log } from "../../system/logger/index.js";
17
- import type { ITaskManager } from "../../events/task-manager/index.js";
18
17
  import { isRecord } from "../../utils/types.js";
19
18
  import { makeUuid } from "../../utils/id.js";
20
19