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.
- package/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
370
|
-
//
|
|
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
|
-
//
|
|
413
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
579
|
+
const { result } = entry;
|
|
727
580
|
if (!isRecord(result)) return [];
|
|
728
581
|
const resultRecord = result as Record<string, unknown>;
|
|
729
|
-
const data = resultRecord
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
753
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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;
|