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
@@ -1,4 +1,4 @@
1
- import { GoogleGenAI, type GenerateContentParameters } from "@google/genai";
1
+ import { GoogleGenAI, type GenerateContentParameters, type GenerateContentResponse, type Part } from "@google/genai";
2
2
  import { env } from "../system/env.js";
3
3
  import { log } from "../system/logger/index.js";
4
4
  import { errorMessage } from "./errors.js";
@@ -29,6 +29,32 @@ export interface GeminiImageResult {
29
29
  message?: string;
30
30
  }
31
31
 
32
+ // Pull the first candidate's `content.parts` array out of a Gemini
33
+ // response, defaulting to `[]` when any layer of the optional chain is
34
+ // absent. Pure — exported for unit tests.
35
+ export function firstCandidateParts(response: GenerateContentResponse): readonly Part[] {
36
+ return response.candidates?.[0]?.content?.parts ?? [];
37
+ }
38
+
39
+ // Pull the first candidate's `finishReason` (used in debug logs).
40
+ // Pure — exported for unit tests.
41
+ export function firstFinishReason(response: GenerateContentResponse): string | undefined {
42
+ return response.candidates?.[0]?.finishReason;
43
+ }
44
+
45
+ // Reduce a Gemini response's `parts` array down to the {imageData,
46
+ // message} pair the rest of the app cares about. Last text wins; last
47
+ // inline-image wins. Parts without text or `inlineData.data` are
48
+ // skipped. Pure — exported for unit tests.
49
+ export function extractImageResult(parts: readonly Part[]): GeminiImageResult {
50
+ const result: GeminiImageResult = {};
51
+ for (const part of parts) {
52
+ if (part.text) result.message = part.text;
53
+ if (part.inlineData?.data) result.imageData = part.inlineData.data;
54
+ }
55
+ return result;
56
+ }
57
+
32
58
  // Low-level wrapper around `ai.models.generateContent` that pulls
33
59
  // the first inline image and text part out of the response. Use this
34
60
  // when you need to pass custom `contents` (e.g. text + reference
@@ -48,7 +74,7 @@ export async function generateGeminiImageContent(
48
74
  const client = getGeminiClient();
49
75
  log.debug("gemini", "generateContent: request", {
50
76
  model,
51
- hasConfig: !!config,
77
+ hasConfig: Boolean(config),
52
78
  aspectRatio: config?.imageConfig?.aspectRatio,
53
79
  });
54
80
  let response;
@@ -62,18 +88,14 @@ export async function generateGeminiImageContent(
62
88
  log.debug("gemini", "generateContent: SDK threw", { model, error: errorMessage(err) });
63
89
  throw err;
64
90
  }
65
- const parts = response.candidates?.[0]?.content?.parts ?? [];
66
- const result: GeminiImageResult = {};
67
- for (const part of parts) {
68
- if (part.text) result.message = part.text;
69
- if (part.inlineData?.data) result.imageData = part.inlineData.data;
70
- }
91
+ const parts = firstCandidateParts(response);
92
+ const result = extractImageResult(parts);
71
93
  log.debug("gemini", "generateContent: response", {
72
94
  model,
73
95
  parts: parts.length,
74
- hasImage: !!result.imageData,
75
- hasText: !!result.message,
76
- finishReason: response.candidates?.[0]?.finishReason,
96
+ hasImage: Boolean(result.imageData),
97
+ hasText: Boolean(result.message),
98
+ finishReason: firstFinishReason(response),
77
99
  });
78
100
  return result;
79
101
  }
@@ -0,0 +1,41 @@
1
+ // Read an HTML artifact file under the `/artifacts/html` static
2
+ // mount and splice two helper scripts before its closing `</body>`:
3
+ // 1. The image-self-repair script (#1011 Stage E / #1025) — restores
4
+ // the behavior that PR #980 unhooked when presentHtml / Files
5
+ // HTML preview moved off `srcdoc` onto the static mount.
6
+ // 2. The iframe height reporter (#1219 follow-up) — lets the parent
7
+ // StackView size the iframe to its rendered content despite the
8
+ // `sandbox="allow-scripts"` cross-origin barrier.
9
+ //
10
+ // Both scripts are pure string injections of trusted server code; the
11
+ // helpers are pure functions and order doesn't matter (one-shot scripts
12
+ // that observe their own document state).
13
+ //
14
+ // Lives in its own module — not inline in `server/index.ts` — so unit
15
+ // tests can import the helpers without dragging the entire server
16
+ // startup as an import side effect.
17
+
18
+ import { readFile as fsReadFile } from "fs/promises";
19
+ import { resolveWithinRoot } from "../files/safe.js";
20
+ import { injectImageRepairScript } from "../../../src/utils/image/imageRepairInlineScript.js";
21
+ import { injectHeightReporterScript } from "../../../src/utils/html/iframeHeightReporterScript.js";
22
+
23
+ /** Read an HTML artifact file (under `htmlsRoot`) and splice the
24
+ * image-self-repair script before its closing `</body>`. Returns
25
+ * the spliced HTML on success, `null` when the file can't be
26
+ * resolved (escapes the root) or read (missing / unreadable).
27
+ *
28
+ * `htmlsRoot` MUST already be a realpath — `resolveWithinRoot`
29
+ * compares against it strictly. The middleware in `server/index.ts`
30
+ * passes the cached `getHtmlsDirReal()` result, which is a realpath. */
31
+ export async function readAndInjectHtmlArtifact(htmlsRoot: string, relPath: string): Promise<string | null> {
32
+ const abs = resolveWithinRoot(htmlsRoot, relPath);
33
+ if (!abs) return null;
34
+ let raw: string;
35
+ try {
36
+ raw = await fsReadFile(abs, "utf8");
37
+ } catch {
38
+ return null;
39
+ }
40
+ return injectHeightReporterScript(injectImageRepairScript(raw));
41
+ }
@@ -0,0 +1,112 @@
1
+ // Server-side mirror of `src/utils/markdown/frontmatter.ts`
2
+ // (#895 PR B). The two share the same shape on purpose so the
3
+ // parser/serializer/merger contract is identical on both sides
4
+ // — what writeWikiPage emits round-trips losslessly through the
5
+ // Vue useMarkdownDoc composable.
6
+ //
7
+ // Code is intentionally NOT a shared package: the wrapper is ~10
8
+ // lines on each side, and a workspace package would need cross-
9
+ // build wiring (browser bundle for Vue, plain Node for server)
10
+ // that's not worth it for so little glue. js-yaml does the heavy
11
+ // lifting in both places, identically.
12
+
13
+ import yaml from "js-yaml";
14
+
15
+ export interface ParsedMarkdown {
16
+ /** Parsed YAML object. Empty `{}` when the document has no
17
+ * frontmatter or the YAML failed to parse. Insertion order
18
+ * matches the source so callers iterating `Object.entries`
19
+ * see fields in the order the file declared them. */
20
+ meta: Record<string, unknown>;
21
+ /** Body after stripping the frontmatter envelope. The trailing
22
+ * newline of the closing `---` line is consumed; a no-frontmatter
23
+ * document returns the raw input verbatim. */
24
+ body: string;
25
+ /** True iff a well-formed `---\n...\n---\n` envelope was
26
+ * detected and parsed. Malformed YAML inside an envelope
27
+ * degrades to `hasHeader: false` so a typo in the header
28
+ * doesn't break writes. */
29
+ hasHeader: boolean;
30
+ }
31
+
32
+ const FRONTMATTER_OPEN = /^---\r?\n/;
33
+ // `(?:^|\r?\n)` lets the closing fence sit at the very start of
34
+ // `afterOpen` — needed for empty envelopes (`---\n---\n`) where
35
+ // the closing `---` is the first thing after the open is stripped.
36
+ const FRONTMATTER_CLOSE = /(?:^|\r?\n)---\s*(?:\r?\n|$)/;
37
+
38
+ /** Parse a markdown document, splitting frontmatter from body.
39
+ * Always returns an object — never throws. */
40
+ export function parseFrontmatter(raw: string): ParsedMarkdown {
41
+ if (!FRONTMATTER_OPEN.test(raw)) {
42
+ return { meta: {}, body: raw, hasHeader: false };
43
+ }
44
+ const afterOpen = raw.replace(FRONTMATTER_OPEN, "");
45
+ const closeMatch = FRONTMATTER_CLOSE.exec(afterOpen);
46
+ if (!closeMatch || closeMatch.index === undefined) {
47
+ return { meta: {}, body: raw, hasHeader: false };
48
+ }
49
+ const yamlText = afterOpen.slice(0, closeMatch.index);
50
+ const body = afterOpen.slice(closeMatch.index + closeMatch[0].length);
51
+ const meta = safeYamlLoad(yamlText);
52
+ if (meta === null) {
53
+ return { meta: {}, body: raw, hasHeader: false };
54
+ }
55
+ return { meta, body, hasHeader: true };
56
+ }
57
+
58
+ /** Serialize a meta object + body back into the canonical
59
+ * `---\n...\n---\n\nbody` shape. An empty `meta` returns the body
60
+ * alone (no envelope) — the lazy-on-write contract: don't add
61
+ * ceremony to documents that don't have anything to record.
62
+ *
63
+ * Round-trip semantics: VALUE-preserving, NOT byte-preserving.
64
+ * `js-yaml` adds quotes to ambiguous scalars (`'1.20'`, `'true'`)
65
+ * so they parse back as the same string under FAILSAFE_SCHEMA.
66
+ * Source-text formatting may change on save but the parsed value
67
+ * is stable across rounds. */
68
+ export function serializeWithFrontmatter(meta: Record<string, unknown>, body: string): string {
69
+ if (Object.keys(meta).length === 0) return body;
70
+ // `lineWidth: -1` disables auto-wrap so long URLs / titles stay on
71
+ // one line. `noRefs: true` avoids YAML anchor syntax (`&id001`)
72
+ // which is technically valid but visually noisy in plain-text
73
+ // markdown.
74
+ const yamlText = yaml.dump(meta, { lineWidth: -1, noRefs: true }).trimEnd();
75
+ return `---\n${yamlText}\n---\n\n${body}`;
76
+ }
77
+
78
+ /** Merge a patch into an existing meta object. Unknown keys in
79
+ * `existing` are preserved verbatim; keys present in `patch`
80
+ * overwrite. A `null` or `undefined` patch value DELETES the key
81
+ * (REST PATCH semantics) — callers that want "leave alone"
82
+ * should omit the key entirely. */
83
+ export function mergeFrontmatter(existing: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {
84
+ const out: Record<string, unknown> = { ...existing };
85
+ for (const [key, value] of Object.entries(patch)) {
86
+ if (value === null || value === undefined) {
87
+ Reflect.deleteProperty(out, key);
88
+ } else {
89
+ out[key] = value;
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+
95
+ function safeYamlLoad(text: string): Record<string, unknown> | null {
96
+ try {
97
+ // `FAILSAFE_SCHEMA` keeps every scalar as a string and skips
98
+ // type coercion. Two motivating cases:
99
+ // - YAML 1.1 dates (`created: 2026-04-27`) would become a
100
+ // `Date` object under DEFAULT_SCHEMA, breaking round-trip.
101
+ // - Numeric-looking strings (`version: 1.20` → 1.2 under
102
+ // JSON_SCHEMA) drop trailing zeros on save.
103
+ // For the wiki-history use case — title / created / updated /
104
+ // tags / editor — every value that should be a string IS one.
105
+ const loaded = yaml.load(text, { schema: yaml.FAILSAFE_SCHEMA });
106
+ if (loaded === null || loaded === undefined) return {};
107
+ if (typeof loaded !== "object" || Array.isArray(loaded)) return null;
108
+ return loaded as Record<string, unknown>;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
@@ -0,0 +1,56 @@
1
+ // Central audit point for server-side regular expressions.
2
+ //
3
+ // Why one file? `eslint-plugin-security`'s `detect-unsafe-regex` rule
4
+ // fires on any regex with a non-trivial nested-quantifier shape, even
5
+ // when surrounding analysis shows the pattern is bounded. Rather than
6
+ // scatter `eslint-disable-next-line` annotations across the server
7
+ // tree, every server regex that the rule flagged lives here with its
8
+ // ReDoS-safety rationale spelled out — security reviews start at this
9
+ // file.
10
+ //
11
+ // Browser-side regexes (`src/utils/format/jsonSyntax.ts`,
12
+ // `src/utils/markdown/taskList.ts`) stay in their consumer files
13
+ // because moving them would force the frontend to import from
14
+ // `server/`. A symmetric `src/utils/regex.ts` could be added later;
15
+ // kept out of scope here so this file has only one architectural
16
+ // layer.
17
+
18
+ // ── Slug validator ────────────────────────────────────────────────
19
+ //
20
+ // Used by `server/utils/slug.ts#isValidSlug` and (transitively)
21
+ // every domain that slugifies user input — todos, wiki, sources,
22
+ // skills. Linear in input length; `[a-z0-9-]*` and `[a-z0-9]` don't
23
+ // share characters with each other or with the boundary `^` / `$`,
24
+ // so the optional capture group can't drive backtracking. Caller
25
+ // length-caps input at `DEFAULT_MAX_LENGTH` (120 chars) BEFORE
26
+ // invoking the regex, so worst-case is ~120 character comparisons.
27
+ //
28
+ // eslint-disable-next-line security/detect-unsafe-regex -- bounded slug validator (length-capped by caller; no nested-quantifier overlap)
29
+ export const SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
30
+
31
+ // ── Wiki bullet-link parsers ──────────────────────────────────────
32
+ //
33
+ // Used by `server/api/routes/wiki.ts` to parse top-of-page index
34
+ // bullets like:
35
+ // - [Title](path/to/page.md) — optional summary
36
+ // - [[wiki-style title]] — optional summary
37
+ // Each `[^x]+` runs over a fixed exclusion set with a hard delimiter
38
+ // (`]`, `)`); the optional summary group's `(.*)` is a single greedy
39
+ // run, no nested overlap. Linear in line length.
40
+ //
41
+ // eslint-disable-next-line security/detect-unsafe-regex -- bullet-link parser; bounded captures with hard delimiters
42
+ export const BULLET_LINK_PATTERN = /^[-*]\s+\[([^\]]+)\]\(([^)]*)\)(?:\s*[—–-]\s*(.*))?/;
43
+ // eslint-disable-next-line security/detect-unsafe-regex -- same shape as BULLET_LINK_PATTERN
44
+ export const BULLET_WIKI_LINK_PATTERN = /^[-*]\s+\[\[([^\]]+)\]\](?:\s*[—–-]\s*(.*))?/;
45
+
46
+ // ── Skills body blank-line stripper ───────────────────────────────
47
+ //
48
+ // Used by `server/workspace/skills/parser.ts` to drop leading blank
49
+ // lines from a skill's body after stripping the frontmatter. `\s*\n`
50
+ // consumes one line at a time with optional leading whitespace; the
51
+ // outer `+` repeats over distinct lines (each iteration MUST consume
52
+ // at least the trailing `\n`), so no overlap → linear in input
53
+ // length.
54
+ //
55
+ // eslint-disable-next-line security/detect-unsafe-regex -- linear blank-line stripper, no nested-quantifier overlap
56
+ export const LEADING_BLANK_LINES_PATTERN = /^(?:\s*\n)+/;
@@ -0,0 +1,41 @@
1
+ // Tiny adapter for registering Express routes from a `ResolvedRoute`
2
+ // `{ method, url }` tuple — the shape host aggregators emit for every
3
+ // plugin-owned route. Lets route files spell the registration as
4
+ // `bindRoute(router, API_ROUTES.todos.itemsCreate, handler)` instead
5
+ // of branching on the verb at the call site.
6
+ //
7
+ // Handler typing matches Express's own loose `RouteParameters`
8
+ // inference: a handler typed as `(req: Request<{ id: string }>, res:
9
+ // Response) => void` is forwarded as-is — the underlying
10
+ // `router[method](url, handler)` call accepts any handler shape, so
11
+ // we cast through `unknown` once here rather than at every site.
12
+
13
+ import type { IRouter, RequestHandler } from "express";
14
+ import type { ResolvedRoute } from "../../src/plugins/meta-types.js";
15
+
16
+ /** Register `handler` on `router` using the verb + URL declared by
17
+ * `route`. The generic on `handler` lets callers pass an Express
18
+ * handler typed against their own `params` / `body` / `query`
19
+ * generics; the cast hides the variance from Express's
20
+ * `RequestHandler<ParamsDictionary, …>` default. */
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ export function bindRoute<H extends (...args: any[]) => unknown>(router: IRouter, route: ResolvedRoute, ...handlers: H[]): void {
23
+ const cast = handlers as unknown as RequestHandler[];
24
+ switch (route.method) {
25
+ case "GET":
26
+ router.get(route.url, ...cast);
27
+ break;
28
+ case "POST":
29
+ router.post(route.url, ...cast);
30
+ break;
31
+ case "PUT":
32
+ router.put(route.url, ...cast);
33
+ break;
34
+ case "PATCH":
35
+ router.patch(route.url, ...cast);
36
+ break;
37
+ case "DELETE":
38
+ router.delete(route.url, ...cast);
39
+ break;
40
+ }
41
+ }
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "crypto";
2
+ import { SLUG_PATTERN } from "./regex.js";
2
3
 
3
4
  // Bits of sha256 kept as the non-ASCII fallback id. 16 base64url chars =
4
5
  // 96 bits; birthday-collision expectation lives at ~2^48 entries, so
@@ -37,7 +38,8 @@ export function hashSlug(input: string, length: number = NON_ASCII_HASH_LEN): st
37
38
  export function isValidSlug(slug: string): boolean {
38
39
  if (typeof slug !== "string") return false;
39
40
  if (slug.length === 0 || slug.length > DEFAULT_MAX_LENGTH) return false;
40
- if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(slug)) return false;
41
+ // Pattern + ReDoS-safety rationale lives in `server/utils/regex.ts`.
42
+ if (!SLUG_PATTERN.test(slug)) return false;
41
43
  if (slug.includes("--")) return false;
42
44
  return true;
43
45
  }
@@ -104,8 +106,8 @@ export function disambiguateSlug(base: string, existingIds: ReadonlySet<string>)
104
106
  }
105
107
 
106
108
  function hasNonHyphenChar(input: string): boolean {
107
- for (let i = 0; i < input.length; i++) {
108
- if (input[i] !== "-") return true;
109
+ for (const char of input) {
110
+ if (char !== "-") return true;
109
111
  }
110
112
  return false;
111
113
  }
@@ -24,6 +24,18 @@ export const TIME_UNIT_MS: Record<string, number> = {
24
24
  /** Quick subprocess probe (docker ps, libreoffice --version, etc.) */
25
25
  export const SUBPROCESS_PROBE_TIMEOUT_MS = 5 * ONE_SECOND_MS;
26
26
 
27
+ /** Debounce window for dev-plugin `dist/` watcher (#1159 PR3). Vite
28
+ * writes 4-5 files within ~100ms on a single rebuild; 300ms collapses
29
+ * the burst into one publish. */
30
+ export const DEV_PLUGIN_WATCH_DEBOUNCE_MS = 300;
31
+
32
+ /** Hard cap on how long the startup-failure path waits for
33
+ * `httpServer.close()` to drain in-flight connections before
34
+ * forcing `process.exit(1)`. SSE streams + WebSocket upgrades hold
35
+ * connections open indefinitely, so the graceful close alone isn't
36
+ * a fail-fast guarantee. */
37
+ export const STARTUP_FAILURE_FORCE_EXIT_MS = 5 * ONE_SECOND_MS;
38
+
27
39
  /** Heavy subprocess work (libreoffice conversion, etc.) */
28
40
  export const SUBPROCESS_WORK_TIMEOUT_MS = ONE_MINUTE_MS;
29
41
 
@@ -8,7 +8,7 @@
8
8
  // at a `mkdtempSync` directory without touching the real
9
9
  // ~/mulmoclaude.
10
10
 
11
- import { readdir, readFile } from "node:fs/promises";
11
+ import { readdir, readFile, rm } from "node:fs/promises";
12
12
  import { defaultSummarize, loadJsonlInput, type SummarizeFn } from "./summarizer.js";
13
13
  import { chatDirFor, indexEntryPathFor, manifestPathFor, sessionJsonlPathFor, sessionMetaPathFor } from "./paths.js";
14
14
  import type { ChatIndexEntry, ChatIndexManifest } from "./types.js";
@@ -104,6 +104,19 @@ export async function updateManifest(workspaceRoot: string, mutator: (m: ChatInd
104
104
  });
105
105
  }
106
106
 
107
+ // Drop a session's entry from both the per-session index file and
108
+ // the shared manifest. Used by the sessions hard-delete route so AI
109
+ // title / summary / keywords don't outlive the underlying jsonl on
110
+ // disk. Both removals tolerate "missing" — sessions that were never
111
+ // indexed have no entry to prune.
112
+ export async function removeSessionFromIndex(workspaceRoot: string, sessionId: string): Promise<void> {
113
+ await rm(indexEntryPathFor(workspaceRoot, sessionId), { force: true });
114
+ await updateManifest(workspaceRoot, (manifest) => ({
115
+ ...manifest,
116
+ entries: manifest.entries.filter((entry) => entry.id !== sessionId),
117
+ }));
118
+ }
119
+
107
120
  // --- freshness check ------------------------------------------------
108
121
 
109
122
  // A session is "fresh" when its per-session index file exists and
@@ -115,7 +128,7 @@ export async function isFresh(workspaceRoot: string, sessionId: string, now: num
115
128
  const raw = await readFile(indexEntryPathFor(workspaceRoot, sessionId), "utf-8");
116
129
  const entry: unknown = JSON.parse(raw);
117
130
  if (!isRecord(entry)) return false;
118
- const indexedAt = (entry as Record<string, unknown>).indexedAt;
131
+ const { indexedAt } = entry as Record<string, unknown>;
119
132
  if (typeof indexedAt !== "string") return false;
120
133
  const indexedTimestamp = Date.parse(indexedAt);
121
134
  if (Number.isNaN(indexedTimestamp)) return false;
@@ -86,7 +86,7 @@ export function extractText(jsonlContent: string): string {
86
86
  } catch {
87
87
  continue;
88
88
  }
89
- const source = entry.source;
89
+ const { source } = entry;
90
90
  if ((source === "user" || source === "assistant") && entry.type === EVENT_TYPES.text && typeof entry.message === "string") {
91
91
  parts.push(`[${source}] ${trimMessage(entry.message)}`);
92
92
  }
@@ -64,7 +64,7 @@ function validatePath(rawPath: string): string | null {
64
64
  if (path.isAbsolute(normalized)) return null;
65
65
 
66
66
  // Not a reserved system directory
67
- if (RESERVED_DIRS.some((reservedDir) => normalized === reservedDir || normalized.startsWith(reservedDir + "/"))) {
67
+ if (RESERVED_DIRS.some((reservedDir) => normalized === reservedDir || normalized.startsWith(`${reservedDir}/`))) {
68
68
  return null;
69
69
  }
70
70
 
@@ -9,7 +9,7 @@ A single key unlocks all three capabilities (images, TTS audio, video) — you d
9
9
  ### Images
10
10
 
11
11
  - **`generateImage`** — creates images from text prompts. The heart of the **Artist** role.
12
- - **`editImage`** — transforms or restyles an existing image ("convert to Ghibli style", "remove the background", "add a sunset"). Also **Artist**.
12
+ - **`editImages`** — transforms, restyles, or combines up to 8 existing images per call ("convert to Ghibli style", "remove the background", "merge these two photos into one"). Also **Artist**.
13
13
  - **Inline document images** — roles that produce rich documents (**Guide & Planner**, **Tutor**, Recipe Guide, Trip Planner, …) embed generated images directly into the page via `presentDocument`. Without a key, those image slots fall back to italic "🖼️ Image: &lt;prompt&gt;" text markers — the prompt is preserved, but no picture renders.
14
14
  - **MulmoScript image beats** — `presentMulmoScript` uses Gemini image models for `imagePrompt` beats. **Storyteller Plus** additionally uses them for consistent-character scenes across a storyboard.
15
15
 
@@ -0,0 +1,61 @@
1
+ # Guide & Planner Templates
2
+
3
+ Reference templates for the Guide & Planner role. Use these structures when authoring documents with `presentDocument` for any of the supported guide types.
4
+
5
+ ## Document Conventions (all types)
6
+
7
+ Every guide should:
8
+
9
+ - **Overview**: open with a summary of the key parameters (servings, days, level, budget, etc.)
10
+ - **Numbered steps OR structured sections**: use one or the other consistently across the document
11
+ - **Anchors**: add `<a id="step-1"></a>` (or `step-N`, `day-N`, `section-name`) to each major heading for `scrollToAnchor` navigation
12
+ - **Images**: embed via `![Detailed image prompt](__too_be_replaced_image_path__)` — make the alt-text prompt specific enough to generate a useful illustration on its own
13
+ - **Close**: end with tips, variations, troubleshooting, or follow-up recommendations
14
+ - **Tone**: warm and encouraging; adapt vocabulary to the user's stated experience level
15
+
16
+ ## Form-First Workflow
17
+
18
+ Always call `presentForm` before producing the document. Tailor fields to the request type:
19
+
20
+ - **Recipe**: servings, dietary restrictions, skill level, available time, equipment
21
+ - **Travel**: destination, dates, traveler count, interests, budget tier, accommodation type
22
+ - **Fitness**: goal (weight loss / strength / endurance), starting level, days per week, equipment access
23
+ - **Event**: occasion, guest count, budget, venue type, dietary needs
24
+ - **Study guide**: topic, current level, time available, learning goal
25
+ - **DIY / home project**: project type, skill level, tools available, budget
26
+
27
+ Pre-fill any field the user already provided via `defaultValue`. Mark fields the user must answer as `required: true`. Keep forms concise — ask only for what is needed to produce a great result.
28
+
29
+ ## Per-Type Document Structures
30
+
31
+ ### Recipe
32
+
33
+ overview → ingredients (scaled to the chosen servings) → equipment → prep work → numbered cooking steps (each with an image) → chef's tips → storage notes
34
+
35
+ ### Travel
36
+
37
+ overview → day-by-day itinerary (morning / afternoon / evening) → accommodation & dining → transport between stops → budget breakdown → packing tips → local culture tips
38
+
39
+ ### Fitness
40
+
41
+ overview → weekly schedule → per-workout breakdown (warm-up, main exercises with sets / reps / form notes, cool-down) → progression plan over weeks → nutrition tips
42
+
43
+ ### Event
44
+
45
+ overview → timeline & checklist (T-minus weeks) → venue & catering → guest list & invitations → décor & entertainment → budget tracker
46
+
47
+ ### Study Guide
48
+
49
+ overview → topic breakdown → key concepts per section → worked examples → practice questions → resources & references
50
+
51
+ ### DIY / Home Project
52
+
53
+ overview → required tools & materials → safety notes → numbered steps (each with an image) → finishing & cleanup → maintenance & care
54
+
55
+ ## Follow-up Pattern
56
+
57
+ After presenting the document:
58
+
59
+ - Offer to read any step aloud (scroll to it first with `scrollToAnchor`, then narrate the step)
60
+ - Invite follow-up questions
61
+ - Offer to adjust the plan based on feedback (regenerate the form, add steps, change scope)
@@ -15,6 +15,7 @@ Under the hood it uses the Claude Code Agent SDK as its LLM core. Claude has ful
15
15
  - **Tutor** — Assesses your knowledge level, then teaches any topic with structured documents and visuals.
16
16
  - **Storyteller** — Crafts illustrated narrative stories as a MulmoScript storyboard.
17
17
  - **Storyteller Plus** — Like Storyteller, with consistent character images across beats.
18
+ - **Settings** — Manages information sources, skills, and scheduled automations for the workspace.
18
19
  - *(Additional roles may be defined by the user in the workspace.)*
19
20
 
20
21
  ## Key Capabilities
@@ -42,7 +43,10 @@ See [Wiki](helps/wiki.md) for details on how it works.
42
43
  - [Gemini API Key](helps/gemini.md) — why `GEMINI_API_KEY` is strongly recommended (images, audio, video) and how to get one from Google AI Studio
43
44
  - [MulmoScript](helps/mulmoscript.md) — format reference for authoring multimedia stories: beats, image types, speech, audio, and a minimal example
44
45
  - [Business Presentation Template](helps/business.md) — MulmoScript template and rules for business presentations in the Office role
46
+ - [Storyteller Template](helps/storyteller.md) — MulmoScript template and rules for character-driven narrated stories in the Storyteller role
47
+ - [Guide & Planner Templates](helps/guide.md) — document structures and form-field hints per guide type for the Guide & Planner role
45
48
  - [Spreadsheet](helps/spreadsheet.md) — cell format, formulas, date handling, and format codes for the presentSpreadsheet plugin
49
+ - [presentHtml](helps/presenthtml.md) — self-contained HTML rules and the three-`../` relative-path convention used by the presentHtml plugin to keep generated files portable under `file://`
46
50
  - [Sandbox](helps/sandbox.md) — how the Docker sandbox isolates the agent, what it can access, and how to disable it
47
51
  - [Telegram Bridge](helps/telegram.md) — how to talk to MulmoClaude from the Telegram app: creating a bot, starting the bridge, allowlisting chat IDs, commands, and troubleshooting
48
52
  - [Information Sources](helps/sources.md) — registering RSS feeds / GitHub repos / arXiv queries, the daily-brief pipeline, and where its files live on disk
@@ -0,0 +1,80 @@
1
+ # presentHtml — Authoring Guide
2
+
3
+ Reference for the `presentHtml` plugin. The HTML you provide is saved to `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html` and rendered in the canvas. The user may also open the file directly from disk via `file://`, so it must remain portable: every URL inside the document has to resolve both when served by the app and when loaded straight off the filesystem.
4
+
5
+ ## Self-Contained Document
6
+
7
+ - Full document including `<!DOCTYPE html>` and `<html>` / `<body>` tags.
8
+ - All CSS and JavaScript inline, or loaded via a public CDN. No local script / stylesheet files (the app does not host arbitrary `.css` / `.js`).
9
+
10
+ ### Allowed CDNs
11
+
12
+ The preview iframe enforces a CSP that only permits a curated set of CDNs. **Use these origins or your script / stylesheet will be silently blocked** and the page will render broken (e.g. `Plotly is not defined` if the chart library was blocked):
13
+
14
+ - `https://cdn.jsdelivr.net` — broadest coverage; preferred for any npm-shaped library
15
+ - `https://unpkg.com` — same scope as jsdelivr; fallback
16
+ - `https://cdnjs.cloudflare.com` — curated mirror; common for older libraries
17
+ - `https://fonts.googleapis.com` + `https://fonts.gstatic.com` — Google Fonts
18
+ - `https://cdn.plot.ly` — Plotly's first-party CDN (also reachable via jsdelivr)
19
+
20
+ When in doubt, pull from `cdn.jsdelivr.net` — it mirrors most npm packages and is always safe. Examples:
21
+
22
+ ```html
23
+ <!-- Plotly via jsdelivr (preferred) -->
24
+ <script src="https://cdn.jsdelivr.net/npm/plotly.js-dist@2/plotly.min.js"></script>
25
+
26
+ <!-- D3 -->
27
+ <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
28
+
29
+ <!-- Tailwind (browser play CDN) -->
30
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
31
+ ```
32
+
33
+ If a library you need is hosted only on a CDN not in this list (e.g. `cdn.example.org`), inline the library code directly — do not link to an unlisted host.
34
+
35
+ ## Referencing Workspace Files
36
+
37
+ The output HTML lives three directory levels deep under `artifacts/`. To reference an image / chart / other artifact, use a **relative path with exactly three `../`** to climb out of `html/<YYYY>/<MM>/`:
38
+
39
+ ```html
40
+ <!-- GOOD -->
41
+ <img src="../../../images/2026/04/foo.png">
42
+
43
+ <!-- BAD: absolute path breaks under file:// -->
44
+ <img src="/artifacts/images/2026/04/foo.png">
45
+
46
+ <!-- BAD: workspace-rooted path resolves against the page URL -->
47
+ <img src="artifacts/images/2026/04/foo.png">
48
+
49
+ <!-- BAD: runtime artifact, not a stored convention -->
50
+ <img src="/api/files/raw?path=artifacts/images/2026/04/foo.png">
51
+ ```
52
+
53
+ Workspace paths returned by other tools (e.g. an image-generating tool returns `artifacts/images/2026/04/foo.png`): replace the leading `artifacts/` with `../../../`, giving `../../../images/2026/04/foo.png`.
54
+
55
+ The same rule applies to anywhere a path appears: `<img>`, `<source>`, `<video poster>`, `<audio src>`, CSS `url(...)`, etc.
56
+
57
+ ## Local Images Not Already Under `artifacts/`
58
+
59
+ If you want to embed a local image that lives **outside** `artifacts/` — e.g. a file the user pasted into `data/attachments/2026/04/foo.png`, a wiki source under `data/wiki/sources/foo.png`, or any other workspace path — **do not link to it directly**. The three-`../` math only resolves cleanly for files under `artifacts/`; references that climb further or sideways break under `file://` and are fragile across workspace reorganisation.
60
+
61
+ Copy the file into `artifacts/images/<YYYY>/<MM>/` first, then reference the copy. Use `mkdir -p` for the partition directory and a UTC year/month matching the convention (`saveImage()` shards by UTC, so doing the same keeps copies grouped with same-month generated images):
62
+
63
+ ```bash
64
+ mkdir -p artifacts/images/2026/04
65
+ cp data/attachments/2026/04/foo.png artifacts/images/2026/04/foo.png
66
+ ```
67
+
68
+ Then in the HTML:
69
+
70
+ ```html
71
+ <img src="../../../images/2026/04/foo.png">
72
+ ```
73
+
74
+ Files **already** under `artifacts/` (e.g. `artifacts/images/2026/03/bar.png`, `artifacts/charts/2026/04/baz.svg`) — reference them in place; do **not** copy.
75
+
76
+ ## Why Three `../`
77
+
78
+ The file is saved at `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html`. From that location, `../../../` climbs out of `<MM>/`, `<YYYY>/`, and `html/` to land in `artifacts/`. From `artifacts/`, the next path segment is the sibling artifact directory you want — `images/`, `charts/`, `documents/`, etc.
79
+
80
+ Absolute paths like `/artifacts/...` work in-app (the server mounts `/artifacts/images` as a static route) but break under `file://`, where root-relative URLs resolve against the filesystem root. Workspace-rooted paths without a leading slash (`artifacts/...`) get joined to the page URL by the browser and 404 every time.
@@ -25,6 +25,13 @@ The container runs with `--cap-drop ALL` and as the host user's UID/GID, so it h
25
25
 
26
26
  Set the environment variable `DISABLE_SANDBOX=1` to always run the agent directly on the host, even when Docker is available.
27
27
 
28
+ ## Debug aids (opt-in env vars)
29
+
30
+ These flags exist for development / debugging only. Off by default so production runs aren't surprised.
31
+
32
+ - `DISABLE_SANDBOX=1` — see above. Bypasses the Docker sandbox.
33
+ - `PERSIST_TOOL_CALLS=1` — also persist `tool_call` events to the session jsonl alongside `tool_result`. Useful for reading the args sent to a tool after the run is over (page refresh / server restart). Off by default because args can be large and may carry payload bytes (inline images, full MulmoScript JSON) you didn't expect to land in the jsonl. See [issue #1096](https://github.com/receptron/mulmoclaude/issues/1096) for the rationale.
34
+
28
35
  ## Host Credentials (opt-in)
29
36
 
30
37
  The sandbox is credential-free by default. Two opt-in flags let you expose the minimum needed for `git` / `gh` to authenticate without leaking private keys into the container: