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,13 +1,3 @@
1
- // The daily pass: walk chat/*.jsonl, find sessions changed since
2
- // the last run, bucket events by local-date, call the archivist
3
- // once per affected day, and apply its output (write daily/*.md,
4
- // create/append/rewrite topics/*.md).
5
- //
6
- // This file is the only one in the journal module that combines
7
- // filesystem side-effects with LLM calls. Pure bits (event parsing,
8
- // bucketing) are factored into small exported helpers so tests can
9
- // exercise them without touching disk.
10
-
11
1
  import fsp from "node:fs/promises";
12
2
  import path from "node:path";
13
3
  import { workspacePath as defaultWorkspacePath } from "../workspace.js";
@@ -37,26 +27,16 @@ import { EVENT_TYPES } from "../../../src/types/events.js";
37
27
  import { extractAndAppendMemory } from "./memoryExtractor.js";
38
28
  import { isRecord } from "../../utils/types.js";
39
29
 
40
- // --- Constants ------------------------------------------------------
41
-
42
- // Per-event content is truncated before handing to the archivist so
43
- // an accidentally huge tool result (e.g. base64 image data) doesn't
44
- // blow past the CLI's context window.
30
+ // Truncate per-event content so an oversized tool result (e.g. base64 image) doesn't blow past the CLI's context window.
45
31
  const MAX_EVENT_CONTENT_CHARS = 600;
46
32
 
47
- // Hard cap on events per session included in the prompt. Sessions
48
- // with thousands of events get their head kept — the archivist can
49
- // generally get the gist from the opening.
33
+ // Cap events per session in the prompt; sessions with thousands keep the head — archivist gets the gist from the opening.
50
34
  const MAX_EVENTS_PER_SESSION = 80;
51
35
 
52
- // --- Public entry ---------------------------------------------------
53
-
54
36
  export interface DailyPassDeps {
55
37
  workspaceRoot?: string;
56
38
  summarize: Summarize;
57
- // Active session ids to skip (mid-write). Caller passes the
58
- // live session registry to avoid ingesting jsonl files that the
59
- // agent is still appending to.
39
+ // Skip mid-write sessions: caller passes the live registry so we don't ingest jsonl the agent is still appending to.
60
40
  activeSessionIds: ReadonlySet<string>;
61
41
  }
62
42
 
@@ -65,18 +45,9 @@ export interface DailyPassResult {
65
45
  sessionsIngested: string[];
66
46
  topicsCreated: string[];
67
47
  topicsUpdated: string[];
68
- skipped: Array<{ date: string; reason: string }>;
69
- }
70
-
71
- // Inputs to the day loop, gathered up-front by `buildDailyPassPlan`
72
- // so `runDailyPass` reads as orchestration only — no IO interleaved
73
- // with scheduling decisions. Returned as a single object instead of
74
- // a tuple so future fields (e.g. caller-provided clock for tests)
75
- // can be added without re-threading every call site.
76
- //
77
- // `newTopicsSeen` is a mutable accumulator: the day loop adds to it
78
- // as topics are created. Including it in the plan keeps the lifecycle
79
- // visible at the call site.
48
+ skipped: { date: string; reason: string }[];
49
+ }
50
+
80
51
  export interface DailyPassPlan {
81
52
  workspaceRoot: string;
82
53
  perSessionExcerpts: Map<string, Map<string, SessionExcerpt>>;
@@ -92,19 +63,8 @@ export interface DailyPassPlan {
92
63
  dirtyMetaById: ReadonlyMap<string, SessionFileMeta>;
93
64
  }
94
65
 
95
- /**
96
- * Read-only setup for `runDailyPass`: enumerate sessions, find the
97
- * dirty ones, build day buckets, snapshot existing topics, and
98
- * compute the initial mutable state. Returns `null` when there's
99
- * nothing to do (no dirty sessions). Otherwise returns a plan object
100
- * the day loop can iterate over.
101
- *
102
- * Intentionally does NOT short-circuit on empty `dayBuckets`. If
103
- * every dirty session produces zero excerpts (all malformed, or all
104
- * tool-only with no text turns) we still want `readAllTopics` to
105
- * fire and `initialNextState.knownTopics` to be normalised/sorted.
106
- * The day loop then iterates zero times and we drop straight through.
107
- */
66
+ // Do NOT short-circuit on empty dayBuckets: if every dirty session yields zero excerpts we still want readAllTopics to
67
+ // fire so initialNextState.knownTopics is normalised/sorted; the day loop then iterates zero times and drops through.
108
68
  export async function buildDailyPassPlan(state: JournalState, deps: DailyPassDeps): Promise<DailyPassPlan | null> {
109
69
  const workspaceRoot = deps.workspaceRoot ?? defaultWorkspacePath;
110
70
  const chatDir = path.join(workspaceRoot, WORKSPACE_DIRS.chat);
@@ -118,11 +78,7 @@ export async function buildDailyPassPlan(state: JournalState, deps: DailyPassDep
118
78
 
119
79
  const existingTopics = await readAllTopics(workspaceRoot);
120
80
  const newTopicsSeen = new Set<string>(state.knownTopics);
121
- // `nextState` is rebuilt through the day loop and persisted after
122
- // each successful day via writeState (atomic tmp+rename). We do
123
- // NOT bump lastDailyRunAt here — that's the outer runner's job
124
- // after the whole pass (including optimization) finishes, so
125
- // partial progress doesn't look like a complete pass.
81
+ // Do NOT bump lastDailyRunAt here outer runner does it after optimization, so partial progress isn't a complete pass.
126
82
  const initialNextState: JournalState = {
127
83
  ...state,
128
84
  knownTopics: [...newTopicsSeen].sort(),
@@ -177,7 +133,7 @@ export async function runDailyPass(state: JournalState, deps: DailyPassDeps): Pr
177
133
  result.topicsUpdated.push(...dayResult.topicsUpdated);
178
134
  result.sessionsIngested.push(...dayResult.sessionsIngested);
179
135
  }
180
- nextState = dayResult.nextState;
136
+ ({ nextState } = dayResult);
181
137
  }
182
138
 
183
139
  await maybeExtractMemory(plan.perSessionExcerpts, plan.workspaceRoot, deps);
@@ -185,10 +141,6 @@ export async function runDailyPass(state: JournalState, deps: DailyPassDeps): Pr
185
141
  return { nextState, result };
186
142
  }
187
143
 
188
- // --- Phase 3 helper: single-day processing + state advance -----------
189
- // Extracted from the Phase 3 for-loop to keep runDailyPass under
190
- // the sonarjs/cognitive-complexity threshold.
191
-
192
144
  interface ProcessDayInput {
193
145
  workspaceRoot: string;
194
146
  date: string;
@@ -244,11 +196,7 @@ async function processDayAndAdvance(input: ProcessDayInput): Promise<ProcessDayO
244
196
  };
245
197
  }
246
198
 
247
- // --- Phase 4 helper: memory extraction -------------------------------
248
- // Scan dirty-session excerpts for durable user facts and append new
249
- // ones to memory.md. Fire-and-forget: if extraction fails the daily
250
- // summaries are already written, so the pass is still useful.
251
-
199
+ // Fire-and-forget: if memory extraction fails the daily summaries are already written, so the pass is still useful.
252
200
  async function maybeExtractMemory(
253
201
  perSessionExcerpts: ReadonlyMap<string, ReadonlyMap<string, SessionExcerpt>>,
254
202
  workspaceRoot: string,
@@ -277,24 +225,16 @@ async function maybeExtractMemory(
277
225
  }
278
226
  }
279
227
 
280
- // --- Phase 3 helper: per-day side-effecting pipeline ----------------
281
-
282
- // Discriminated return so `runDailyPass` can branch on outcome
283
- // without digging into null or throwing.
284
228
  export type DayOutcome =
285
229
  | { kind: "skipped"; reason: string }
286
230
  | {
287
231
  kind: "processed";
288
232
  topicsCreated: string[];
289
233
  topicsUpdated: string[];
290
- // Union of created + updated handed back so the caller
291
- // can keep `newTopicsSeen` in sync without recomputing.
234
+ // Union of created + updated so the caller can keep newTopicsSeen in sync without recomputing.
292
235
  topicsTouched: string[];
293
236
  };
294
237
 
295
- // Run the archivist for one day and apply its output (daily
296
- // summary + topic updates). All filesystem writes land here so
297
- // `runDailyPass` stays branching-lean.
298
238
  async function processOneDay(
299
239
  workspaceRoot: string,
300
240
  date: string,
@@ -335,10 +275,7 @@ async function processOneDay(
335
275
  };
336
276
  }
337
277
 
338
- // Call the archivist summarizer and narrow its failure modes.
339
- // Returns null on recoverable failures (logged + skipped), throws
340
- // only for `ClaudeCliNotFoundError` which the outer runner uses to
341
- // disable the whole journal feature for the process lifetime.
278
+ // Throws only for ClaudeCliNotFoundError (outer runner uses it to disable the whole journal feature for the process lifetime).
342
279
  async function callSummarizeForDay(date: string, input: DailyArchivistInput, summarize: Summarize): Promise<string | null> {
343
280
  try {
344
281
  return await summarize(DAILY_SYSTEM_PROMPT, buildDailyUserPrompt(input));
@@ -352,27 +289,16 @@ async function callSummarizeForDay(date: string, input: DailyArchivistInput, sum
352
289
  }
353
290
  }
354
291
 
355
- // Side-effecting wrapper: rewrite workspace-absolute links in the
356
- // archivist output relative to the daily summary's own location,
357
- // then write the file to disk. Factored out so the main loop's
358
- // body no longer contains path-math and I/O intermixed.
359
292
  async function writeDailySummaryForDate(workspaceRoot: string, date: string, rawMarkdown: string): Promise<void> {
360
- // Rewrite any /workspace-absolute links in the archivist's output
361
- // into true-relative links from the daily summary's location
362
- // before writing to disk. Same treatment below for topic files.
293
+ // Rewrite /workspace-absolute links into true-relative links from the daily summary's location (same for topic files).
363
294
  const [yearPart, monthPart, dayPart] = date.split("-");
364
295
  const dailyFileWsPath = path.posix.join(WORKSPACE_DIRS.summaries, "daily", yearPart, monthPart, `${dayPart}.md`);
365
296
  const content = rewriteWorkspaceLinks(dailyFileWsPath, rawMarkdown);
366
297
  await writeDailySummary(date, content, workspaceRoot);
367
298
  }
368
299
 
369
- // Apply every topic update the archivist asked for, keeping the
370
- // in-memory `existingTopics` snapshot in sync so the next day in
371
- // this same pass sees fresh content. Mutates `existingTopics`.
372
- //
373
- // Per-update failures (EACCES, EIO, etc. surfaced by appendOrCreate)
374
- // are logged and skipped so a single broken topic file doesn't kill
375
- // the whole pass after days of progress have already been committed.
300
+ // Mutates existingTopics so the next day in the same pass sees fresh content. Per-update failures are logged + skipped
301
+ // so one broken topic file doesn't kill the whole pass after days of progress have already been committed.
376
302
  async function processTopicUpdatesForDay(
377
303
  workspaceRoot: string,
378
304
  updates: readonly TopicUpdate[],
@@ -397,9 +323,6 @@ async function processTopicUpdatesForDay(
397
323
  return { created, updated };
398
324
  }
399
325
 
400
- // Re-read the topic file fresh and upsert its snapshot into the
401
- // in-memory `existingTopics` list so the next day's archivist
402
- // call sees the latest content.
403
326
  async function refreshTopicSnapshot(workspaceRoot: string, slug: string, existingTopics: ExistingTopicSnapshot[]): Promise<void> {
404
327
  const newBody = await readTopicFile(slug, workspaceRoot);
405
328
  if (newBody === null) return;
@@ -409,11 +332,8 @@ async function refreshTopicSnapshot(workspaceRoot: string, slug: string, existin
409
332
  else existingTopics[idx] = snapshot;
410
333
  }
411
334
 
412
- // Persist the in-progress journal state after each day so a
413
- // mid-pass crash only costs the work written after the last
414
- // checkpoint. Write failures are logged but don't fail the pass —
415
- // the day's markdown is already on disk and the next run will
416
- // catch up.
335
+ // Checkpoint state after each day so a mid-pass crash only costs work since the last write. Write failures don't fail
336
+ // the pass the day's markdown is already on disk and the next run catches up.
417
337
  async function persistStateAfterDay(workspaceRoot: string, state: JournalState, date: string): Promise<void> {
418
338
  try {
419
339
  await writeState(workspaceRoot, state);
@@ -425,17 +345,8 @@ async function persistStateAfterDay(workspaceRoot: string, state: JournalState,
425
345
  }
426
346
  }
427
347
 
428
- // --- Pure helpers (exported for unit tests) ------------------------
429
-
430
- // Bucket every session's per-date excerpts into a `dayBuckets`
431
- // map and a `sessionToDays` tracking map in one pass. Inputs are
432
- // the per-session excerpts loaded by `loadDirtySessionExcerpts`;
433
- // outputs are plain Maps the day loop can consume directly.
434
- //
435
- // `sessionToDays` is used later by `computeJustCompletedSessions`
436
- // to mark a session fully processed only after its last day has
437
- // been written. That's why both Maps are built here together —
438
- // they're two views of the same input and staying in sync matters.
348
+ // dayBuckets and sessionToDays must stay in sync — sessionToDays drives "session fully processed only after its last
349
+ // day has been written" downstream in computeJustCompletedSessions.
439
350
  export interface DayBucketsPlan {
440
351
  dayBuckets: Map<string, SessionExcerpt[]>;
441
352
  sessionToDays: Map<string, Set<string>>;
@@ -461,12 +372,8 @@ export function buildDayBuckets(perSessionExcerpts: ReadonlyMap<string, Readonly
461
372
  return { dayBuckets, sessionToDays };
462
373
  }
463
374
 
464
- // Apply the append-to-missing create guard and canonicalise
465
- // the slug. The archivist occasionally asks to "append" to a
466
- // brand-new topic; silently promoting that to "create" removes a
375
+ // The archivist occasionally asks to "append" to a brand-new topic; silently promoting that to "create" removes a
467
376
  // whole class of LLM mistakes without needing a schema rejection.
468
- // Also rewrites any workspace-absolute links in the body relative
469
- // to the target topic file's location.
470
377
  export function normalizeTopicAction(update: TopicUpdate, existingTopics: readonly ExistingTopicSnapshot[]): TopicUpdate {
471
378
  const canonicalSlug = slugify(update.slug);
472
379
  const exists = existingTopics.some((topic) => topic.slug === canonicalSlug);
@@ -478,29 +385,13 @@ export function normalizeTopicAction(update: TopicUpdate, existingTopics: readon
478
385
  };
479
386
  }
480
387
 
481
- // Parse an archivist raw output string into a validated
482
- // DailyArchivistOutput. Returns null when the JSON envelope is
483
- // missing or the shape doesn't match, so callers can treat it
484
- // as a skip reason without needing a separate `isValid` check.
485
- // Pure — combines `extractJsonObject` + `isDailyArchivistOutput`
486
- // behind a single gate.
487
388
  export function parseArchivistOutput(rawOutput: string): DailyArchivistOutput | null {
488
389
  const parsed = extractJsonObject(rawOutput);
489
390
  if (!isDailyArchivistOutput(parsed)) return null;
490
391
  return parsed;
491
392
  }
492
393
 
493
- // Decide which sessions have just completed their last pending
494
- // day, mutating `sessionToDays` to drop `date` from every entry
495
- // that touches it. Returns the `SessionFileMeta` records for the
496
- // freshly-completed sessions so the caller can feed them into
497
- // `applyProcessed`.
498
- //
499
- // A session is "complete" when its pending-days set, *after*
500
- // removing the current date, is empty. Sessions not in
501
- // `sessionToDays` (or not in `dirtyMetaById`) are silently
502
- // skipped — defensive against unexpected inputs, and the same
503
- // shape as the pre-refactor inline code.
394
+ // Mutates sessionToDays. A session is "complete" when its pending-days set is empty AFTER removing the current date.
504
395
  export function computeJustCompletedSessions(
505
396
  date: string,
506
397
  excerpts: readonly SessionExcerpt[],
@@ -521,11 +412,6 @@ export function computeJustCompletedSessions(
521
412
  return justCompleted;
522
413
  }
523
414
 
524
- // Build the next JournalState from the previous one plus a batch
525
- // of just-completed sessions and the current view of known
526
- // topics. Tiny pure wrapper so the day loop has one place to
527
- // advance state instead of five-line spread literals scattered
528
- // through it.
529
415
  export function advanceJournalState(prev: JournalState, justCompleted: readonly SessionFileMeta[], newTopicsSeen: ReadonlySet<string>): JournalState {
530
416
  return {
531
417
  ...prev,
@@ -534,13 +420,7 @@ export function advanceJournalState(prev: JournalState, justCompleted: readonly
534
420
  };
535
421
  }
536
422
 
537
- // --- Filesystem helpers ---------------------------------------------
538
-
539
- // Load every dirty session's jsonl, bucket events by local-date,
540
- // and return the whole collection as a Map<sessionId, Map<date,
541
- // excerpt>>. Malformed sessions are logged and skipped so one
542
- // bad jsonl can't crash the pass. Returned shape is exactly what
543
- // `buildDayBuckets` wants as input.
423
+ // Malformed sessions are logged and skipped so one bad jsonl can't crash the pass.
544
424
  async function loadDirtySessionExcerpts(chatDir: string, dirty: readonly string[], workspaceRoot: string): Promise<Map<string, Map<string, SessionExcerpt>>> {
545
425
  const perSession = new Map<string, Map<string, SessionExcerpt>>();
546
426
  for (const sessionId of dirty) {
@@ -593,10 +473,6 @@ async function loadSessionExcerptsByDate(chatDir: string, sessionId: string, wor
593
473
  return bucketParsedEvents(parsedEvents, sessionId, roleId, fallbackDate);
594
474
  }
595
475
 
596
- // Walk a jsonl string and return at most `maxEvents` parsed events
597
- // ready for bucketing. Skips blank lines, malformed JSON,
598
- // metadata entries, and anything `parseEntry` rejects. Pure —
599
- // exported so tests can exercise it with fabricated jsonl strings.
600
476
  export function parseJsonlEvents(raw: string, maxEvents: number): ParsedEntry[] {
601
477
  const events: ParsedEntry[] = [];
602
478
  for (const line of raw.split("\n")) {
@@ -610,14 +486,7 @@ export function parseJsonlEvents(raw: string, maxEvents: number): ParsedEntry[]
610
486
  return events;
611
487
  }
612
488
 
613
- // JSON.parse one jsonl line, guarding against blank lines,
614
- // malformed JSON, and any JSON value that isn't a plain object.
615
- // `JSON.parse` will happily return `null`, arrays, strings,
616
- // numbers, or booleans, none of which the downstream
617
- // `parseEntry` / `entryToExcerpt` functions can consume — and
618
- // `entry.type` on a `null` or primitive throws at runtime.
619
- // Returning `null` here collapses every invalid shape into the
620
- // same "skip this line" sentinel the caller already handles.
489
+ // Reject non-object JSON (null, arrays, primitives) — entry.type would throw on them, and downstream parseEntry can't consume them.
621
490
  function parseJsonlLine(line: string): Record<string, unknown> | null {
622
491
  if (!line.trim()) return null;
623
492
  let parsed: unknown;
@@ -636,10 +505,7 @@ function isMetadataEntry(entry: Record<string, unknown>): boolean {
636
505
  return entry.type === EVENT_TYPES.sessionMeta || entry.type === EVENT_TYPES.claudeSessionId;
637
506
  }
638
507
 
639
- // Collect parsed events into per-date buckets using `fallbackDate`
640
- // for every event, since the legacy jsonl format has no per-event
641
- // timestamps. Extracted so the I/O-free bucket-building can be
642
- // reasoned about and unit-tested without a real jsonl file.
508
+ // Uses fallbackDate for every event because the legacy jsonl format has no per-event timestamps.
643
509
  export function bucketParsedEvents(events: readonly ParsedEntry[], sessionId: string, roleId: string, fallbackDate: string): Map<string, SessionExcerpt> {
644
510
  const buckets = new Map<string, SessionExcerpt>();
645
511
  for (const parsed of events) {
@@ -666,13 +532,8 @@ async function readRoleIdFromMeta(sessionId: string, workspaceRoot: string): Pro
666
532
  return "unknown";
667
533
  }
668
534
 
669
- // Convert one jsonl entry into a flat excerpt the archivist can read,
670
- // plus any workspace-relative artifact paths the entry references.
671
- // Exported so tests can exercise it with fabricated entries.
672
535
  export interface ParsedEntry {
673
536
  excerpt: SessionEventExcerpt;
674
- // 0+ workspace-relative artifact paths referenced by this entry.
675
- // Used to build the ARTIFACTS REFERENCED prompt section.
676
537
  artifactPaths: string[];
677
538
  }
678
539
 
@@ -685,13 +546,11 @@ export function parseEntry(entry: Record<string, unknown>): ParsedEntry | null {
685
546
  };
686
547
  }
687
548
 
688
- // Legacy single-purpose form used by the existing unit tests.
689
- // Prefer `parseEntry` for code that also wants artifact paths.
549
+ // Prefer parseEntry for code that also wants artifact paths; this form is kept for the existing unit tests.
690
550
  export function entryToExcerpt(entry: Record<string, unknown>): SessionEventExcerpt | null {
691
551
  const source = typeof entry.source === "string" ? entry.source : "unknown";
692
552
  const type = typeof entry.type === "string" ? entry.type : "unknown";
693
553
 
694
- // text entries: {source, type: "text", message}
695
554
  if (type === EVENT_TYPES.text && typeof entry.message === "string") {
696
555
  return {
697
556
  source,
@@ -699,10 +558,7 @@ export function entryToExcerpt(entry: Record<string, unknown>): SessionEventExce
699
558
  content: truncate(entry.message, MAX_EVENT_CONTENT_CHARS),
700
559
  };
701
560
  }
702
- // tool_result entries: {source: "tool", type: "tool_result", result: {toolName, message, ...}}
703
- // `typeof null === "object"` so we must explicitly reject null
704
- // to avoid a NullPointerException-style crash when accessing
705
- // r.toolName below.
561
+ // typeof null === "object", so isRecord must reject null before accessing resultRecord.toolName below.
706
562
  if (type === EVENT_TYPES.toolResult && isRecord(entry.result)) {
707
563
  const resultRecord = entry.result as Record<string, unknown>;
708
564
  const toolName = typeof resultRecord.toolName === "string" ? resultRecord.toolName : "tool";
@@ -717,41 +573,32 @@ export function entryToExcerpt(entry: Record<string, unknown>): SessionEventExce
717
573
  return null;
718
574
  }
719
575
 
720
- // Pull workspace-relative artifact paths out of a jsonl entry. The
721
- // extraction is tool-aware: different plugins stash file paths in
722
- // different places inside their tool_result data. Exported for
723
- // tests.
576
+ // Tool-aware extraction: different plugins stash file paths in different places inside tool_result data.
724
577
  export function extractArtifactPaths(entry: Record<string, unknown>): string[] {
725
578
  if (entry.type !== "tool_result") return [];
726
- const result = entry.result;
579
+ const { result } = entry;
727
580
  if (!isRecord(result)) return [];
728
581
  const resultRecord = result as Record<string, unknown>;
729
- const data = resultRecord.data;
582
+ const { data } = resultRecord;
730
583
  if (!isRecord(data)) return [];
731
584
  const dataRecord = data as Record<string, unknown>;
732
585
  const paths: string[] = [];
733
586
 
734
- // Direct `filePath: string` presentMulmoScript, presentHtml.
587
+ // presentMulmoScript / presentHtml expose filePath directly.
735
588
  if (typeof dataRecord.filePath === "string" && dataRecord.filePath.length > 0) {
736
589
  paths.push(dataRecord.filePath);
737
590
  }
738
591
 
739
- // Wiki uses `pageName: string` and stores the page at
740
- // `wiki/pages/<pageName>.md`. The plugin itself doesn't surface
741
- // the full path in the result, so we synthesise it from the
742
- // convention established in server/routes/wiki.ts.
592
+ // manageWiki only surfaces pageName; synthesise the path from the wiki/pages/<pageName>.md convention.
743
593
  if (resultRecord.toolName === "manageWiki" && typeof dataRecord.pageName === "string") {
744
594
  paths.push(`wiki/pages/${dataRecord.pageName}.md`);
745
595
  }
746
596
 
747
- // Paths must be workspace-relative (not absolute, no escape).
748
- // Drop anything suspicious rather than link to it.
749
597
  return paths.filter(isSafeWorkspacePath);
750
598
  }
751
599
 
752
- // Defensive: refuse absolute paths, parent-escapes, or scheme-like
753
- // strings. Protects against a malformed tool result wedging a
754
- // filesystem-absolute path into the archivist prompt.
600
+ // Refuse absolute paths, parent-escapes, scheme-like strings — guards against a malformed tool result wedging an
601
+ // absolute filesystem path into the archivist prompt.
755
602
  function isSafeWorkspacePath(candidatePath: string): boolean {
756
603
  if (!candidatePath) return false;
757
604
  if (candidatePath.startsWith("/")) return false;
@@ -1,40 +1,18 @@
1
- // "Which sessions are new or have changed since the last journal
2
- // run?" — pure logic that takes in-memory representations of the
3
- // current filesystem and the persisted state, and returns the list
4
- // of session ids that need re-ingest.
5
- //
6
- // Extracted from the filesystem layer so tests can exercise it with
7
- // hand-rolled inputs instead of mocking `fs`.
8
-
9
1
  import type { JournalState, ProcessedSessionRecord } from "./state.js";
10
2
 
11
3
  export interface SessionFileMeta {
12
- // Session id (matches the .jsonl filename without extension).
13
4
  id: string;
14
- // mtime in ms since epoch. The only signal we use to detect
15
- // appends — sessions don't have a version counter.
5
+ // mtime in ms sessions have no version counter, so this is the only signal we have for "file changed".
16
6
  mtimeMs: number;
17
7
  }
18
8
 
19
9
  export interface DirtySessionDecision {
20
10
  dirty: string[];
21
- // Sessions already in state whose files have vanished from disk.
22
- // We keep them in the state record (no harm) but the caller may
23
- // choose to prune them separately.
11
+ // Previously-processed sessions whose files have vanished informational, not an error.
24
12
  missing: string[];
25
13
  }
26
14
 
27
- // Core diff. Given the current directory listing and the persisted
28
- // processed-sessions record, return:
29
- // - `dirty`: sessions that were never seen, or whose mtime has
30
- // advanced since we last ingested them, or whose mtime we don't
31
- // have a record of (treat as dirty — safer to re-ingest than miss).
32
- // - `missing`: sessions we had previously processed that no longer
33
- // exist on disk. Not an error, just information.
34
- //
35
- // The caller may additionally exclude currently-active sessions
36
- // (whose jsonl could be mid-write); that's a separate concern and
37
- // kept out of the pure diff.
15
+ // Treats unknown-mtime as dirty (safer to re-ingest than miss). Active mid-write sessions are filtered by the caller.
38
16
  export function findDirtySessions(current: readonly SessionFileMeta[], processed: Record<string, ProcessedSessionRecord>): DirtySessionDecision {
39
17
  const dirty: string[] = [];
40
18
  const seenNow = new Set<string>();
@@ -59,9 +37,6 @@ export function findDirtySessions(current: readonly SessionFileMeta[], processed
59
37
  return { dirty, missing };
60
38
  }
61
39
 
62
- // Produce the next processedSessions map after a successful ingest
63
- // of the given dirty ids. Pure — doesn't mutate input. Sessions not
64
- // in the dirty list keep their existing record.
65
40
  export function applyProcessed(previous: JournalState["processedSessions"], justProcessed: readonly SessionFileMeta[]): JournalState["processedSessions"] {
66
41
  const next: JournalState["processedSessions"] = { ...previous };
67
42
  for (const meta of justProcessed) {
@@ -1,50 +1,7 @@
1
- // Public entry point for the workspace journal. The agent route
2
- // calls `maybeRunJournal()` from its `finally` block fire-and-
3
- // forget. This module decides whether a pass is actually due, holds
4
- // an in-process lock so concurrent sessions don't double-run,
5
- // orchestrates daily + optimization passes, and rebuilds _index.md.
6
- //
7
- // All failures are caught and logged here; nothing ever bubbles
8
- // back to the request handler.
9
- //
10
- // ── Architecture (#799) ──────────────────────────────────────────
11
- //
12
- // Two top-level passes hang off `maybeRunJournal()`, each with its
13
- // own cadence and its own state-machine slot. Memory extraction is
14
- // a sub-step of the daily pass, not a peer pipeline.
15
- //
16
- // session-end
17
- // │
18
- // └─> maybeRunJournal() [this file]
19
- // │ gates by interval, holds the lock, traps errors
20
- // │
21
- // ├─> runDailyPass (≥ 1 h since last)
22
- // │ dailyPass.ts finds new/changed sessions via
23
- // │ mtime, buckets events by local
24
- // │ date, calls `claude` CLI once
25
- // │ per day, writes daily summaries
26
- // │ + topics + state checkpoint.
27
- // │ │
28
- // │ │ early-returns when no dirty sessions; otherwise
29
- // │ │ at the end of the per-day loop:
30
- // │ │
31
- // │ └─> extractAndAppendMemory
32
- // │ memoryExtractor.ts scans the day's new
33
- // │ daily file for memory-
34
- // │ worthy facts, appends to
35
- // │ the user's ~/.claude/
36
- // │ memory.md.
37
- // │
38
- // └─> runOptimizationPass (≥ 7 d since last)
39
- // optimizationPass.ts reads existing topics, asks
40
- // the LLM to merge duplicates /
41
- // archive stale ones, writes back
42
- // and updates archive/.
43
- //
44
- // _index.md is rebuilt at the end of every successful pass so the
45
- // UI's directory listing reflects whatever was just written.
46
- //
47
- // Audit + roadmap: `plans/audit-journal-subsystem.md` (#799).
1
+ // Architecture (#799) agent route calls maybeRunJournal() fire-and-forget from its finally block. The daily pass
2
+ // (≥1h since last) walks chat/*.jsonl by mtime, buckets events by local date, writes daily summaries + topics, then
3
+ // extractAndAppendMemory appends durable user facts to memory.md. The optimization pass (≥7d) merges/archives topics.
4
+ // _index.md is rebuilt at the end of every successful pass. Roadmap: plans/audit-journal-subsystem.md.
48
5
 
49
6
  import { workspacePath as defaultWorkspacePath } from "../workspace.js";
50
7
  import {
@@ -64,42 +21,26 @@ import { log } from "../../system/logger/index.js";
64
21
 
65
22
  export { extractFirstH1 };
66
23
 
67
- // Module-level lock. A boolean is enough for the single-process
68
- // single-user MulmoClaude server; if two sessions finish at the
69
- // same instant, the second call returns immediately.
24
+ // Single-process server, so a boolean is enough; a second concurrent call simply returns.
70
25
  let running = false;
71
26
 
72
- // Once we hit ENOENT on the `claude` CLI we disable the journal
73
- // for the rest of the server lifetime to avoid spamming warnings
74
- // on every session-end. Reset on server restart.
27
+ // Latch off the journal for the rest of the process once the claude CLI is missing avoids spamming on every session-end.
75
28
  let disabled = false;
76
29
 
77
- // Test-only reset for the module-level flags. The lock + disable
78
- // flags are intentionally module-scoped so a fresh server start
79
- // always begins from "neither running nor disabled"; a unit test
80
- // that exercises maybeRunJournal across multiple call sequences
81
- // needs a way to reproduce that fresh-start condition without
82
- // re-importing the module. Not exported via index — only consumers
83
- // importing this file directly should reach for it.
30
+ // Reset module-level flags between unit-test runs without re-importing the module.
84
31
  export function __resetForTests(): void {
85
32
  running = false;
86
33
  disabled = false;
87
34
  }
88
35
 
89
- // The agent route calls this as `maybeRunJournal().catch(...)`.
90
36
  export interface MaybeRunJournalOptions {
91
37
  summarize?: Summarize;
92
38
  workspaceRoot?: string;
93
39
  activeSessionIds?: ReadonlySet<string>;
94
- // Skip the interval check and run both passes unconditionally.
95
- // Useful for debugging / CLI-driven manual runs — the feature's
96
- // disable flags (claude CLI missing, in-process lock) still apply.
40
+ // Bypass the interval gate; the disable flags (CLI missing, in-process lock) still apply.
97
41
  force?: boolean;
98
42
  }
99
43
 
100
- // Everything inside swallows its own errors so the promise never
101
- // rejects in practice, but we still attach a catch at the call
102
- // site defensively.
103
44
  export async function maybeRunJournal(opts: MaybeRunJournalOptions = {}): Promise<void> {
104
45
  if (disabled) return;
105
46
  if (running) return;
@@ -128,9 +69,6 @@ async function runJournalPass(opts: MaybeRunJournalOptions): Promise<void> {
128
69
  const state = await readState(workspaceRoot);
129
70
  const now = Date.now();
130
71
 
131
- // `force: true` bypasses the interval gate entirely so debug /
132
- // startup flows can trigger a full pass even when nothing is
133
- // technically due.
134
72
  const daily = opts.force === true || isDailyDue(state, now);
135
73
  const optimize = opts.force === true || isOptimizationDue(state, now);
136
74
  if (!daily && !optimize) return;
@@ -147,9 +85,7 @@ async function runJournalPass(opts: MaybeRunJournalOptions): Promise<void> {
147
85
  summarize,
148
86
  activeSessionIds,
149
87
  });
150
- // Only advance lastDailyRunAt when no days were skipped —
151
- // otherwise we'd wait a full interval before retrying a failed
152
- // day, letting transient archivist failures silently lose events.
88
+ // Only bump lastDailyRunAt when no days were skipped — otherwise transient archivist failures silently lose events.
153
89
  nextState = {
154
90
  ...afterDaily,
155
91
  ...(result.skipped.length === 0 && {
@@ -168,11 +104,7 @@ async function runJournalPass(opts: MaybeRunJournalOptions): Promise<void> {
168
104
  if (optimize) {
169
105
  log.info("journal", "running optimization pass");
170
106
  const { nextState: afterOpt, result } = await runOptimizationPass(nextState, { workspaceRoot, summarize });
171
- // Same rule as daily: only advance the timestamp when the pass
172
- // actually ran to completion. A "skipped: too few topics" case
173
- // is still considered successful — there was simply nothing to
174
- // do — and we allow it to bump so we don't re-check on every
175
- // session-end.
107
+ // Same rule as daily, except "fewer than 2 topics" is still success — bump so we don't re-check every session-end.
176
108
  const optimizationSucceeded = !result.skipped || result.skippedReason === "fewer than 2 topics";
177
109
  nextState = {
178
110
  ...afterOpt,
@@ -196,8 +128,6 @@ async function runJournalPass(opts: MaybeRunJournalOptions): Promise<void> {
196
128
  await writeState(workspaceRoot, nextState);
197
129
  }
198
130
 
199
- // --- Index rebuild -------------------------------------------------
200
-
201
131
  async function rebuildIndex(workspaceRoot: string): Promise<void> {
202
132
  const topics = await walkTopics(workspaceRoot);
203
133
  const dailyEntries = await listDailyFilesIO(workspaceRoot);
@@ -229,7 +159,6 @@ async function walkTopics(workspaceRoot: string): Promise<IndexTopicEntry[]> {
229
159
 
230
160
  const DAY_FILE_PATTERN = /^(\d{2})\.md$/;
231
161
 
232
- // Pure: returns the two-digit day if `name` matches `DD.md`, else null.
233
162
  export function parseDailyFilename(name: string): string | null {
234
163
  const match = DAY_FILE_PATTERN.exec(name);
235
164
  return match ? (match[1] ?? null) : null;