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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// LLM-backed `MemoryClassifier` for the legacy-memory migration
|
|
2
|
+
// (#1029 PR-B). Asks Claude to look at a single legacy bullet plus
|
|
3
|
+
// its surrounding H2 section header and produce a JSON verdict
|
|
4
|
+
// `{type, description}`. The four types and their meaning live in
|
|
5
|
+
// the system prompt so the LLM can pick consistently across
|
|
6
|
+
// candidates.
|
|
7
|
+
//
|
|
8
|
+
// CLEANUP 2026-07-01: see `run.ts` — this file is part of the
|
|
9
|
+
// one-shot `memory.md` → atomic migration chain and goes when the
|
|
10
|
+
// chain goes.
|
|
11
|
+
//
|
|
12
|
+
// The function is intentionally one-call-per-candidate (not a batch
|
|
13
|
+
// classify): batching saves tokens but couples failures together, so
|
|
14
|
+
// a single malformed verdict could poison the whole batch. Migration
|
|
15
|
+
// is one-time and small — keep the request shape simple.
|
|
16
|
+
|
|
17
|
+
import type { MemoryClassification, MemoryCandidate, MemoryClassifier } from "./migrate.js";
|
|
18
|
+
import { isMemoryType, type MemoryType } from "./types.js";
|
|
19
|
+
|
|
20
|
+
import { ClaudeCliNotFoundError, type Summarize } from "../journal/archivist-cli.js";
|
|
21
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
22
|
+
import { log } from "../../system/logger/index.js";
|
|
23
|
+
|
|
24
|
+
const SYSTEM_PROMPT = `You are classifying a single bullet from a personal-memory file into one of four types.
|
|
25
|
+
|
|
26
|
+
Types:
|
|
27
|
+
- preference: a durable habit, preference, or convention that does not change over time. Examples: "uses yarn (npm not allowed)", "prefers Emacs", "writes commit messages in English".
|
|
28
|
+
- interest: a topic, hobby, or domain the user follows over a long horizon. Examples: "AI research papers", "robotics", "Impressionist painting".
|
|
29
|
+
- fact: a concrete personal fact that COULD become stale. Examples: "planning a trip to Egypt", "owns a toaster oven", "currently working on BootCamp project".
|
|
30
|
+
- reference: a pointer to an internal/external resource (path, dashboard, recurring task id, repo URL). Examples: "main repo at ~/ss/llm/mulmoclaude4", "weekly art-exhibitions-watch task".
|
|
31
|
+
|
|
32
|
+
Rules:
|
|
33
|
+
- Output ONE compact JSON object. No prose, no markdown, no code fences. Output literal JSON only.
|
|
34
|
+
- Schema: {"type":"<one of preference|interest|fact|reference>","description":"<short single-line description, <=100 chars>"}
|
|
35
|
+
- The description is for an index file. Keep it short and informative — strip filler, don't repeat the bullet verbatim.
|
|
36
|
+
- If the bullet is too vague to classify, output: null
|
|
37
|
+
- Never invent fields. Output exactly the schema above or null.`;
|
|
38
|
+
|
|
39
|
+
interface DepsForLlmClassifier {
|
|
40
|
+
/** Same Summarize callback used by `journal/dailyPass`. The
|
|
41
|
+
* classifier feeds it the system prompt + a single-bullet user
|
|
42
|
+
* prompt and parses the JSON reply. */
|
|
43
|
+
summarize: Summarize;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function makeLlmMemoryClassifier(deps: DepsForLlmClassifier): MemoryClassifier {
|
|
47
|
+
return async (candidate: MemoryCandidate) => {
|
|
48
|
+
const userPrompt = buildUserPrompt(candidate);
|
|
49
|
+
let raw: string;
|
|
50
|
+
try {
|
|
51
|
+
raw = await deps.summarize(SYSTEM_PROMPT, userPrompt);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// Let `ClaudeCliNotFoundError` escape so the caller can abort
|
|
54
|
+
// the whole migration. Without this, every candidate runs
|
|
55
|
+
// through the classifier, gets `null` back, and migrateLegacyMemory
|
|
56
|
+
// produces an empty result + (pre-#1091) renames the legacy file
|
|
57
|
+
// to `.backup`. Even with #1091 the user pays N classifier
|
|
58
|
+
// failures (each spawns claude) before realising the CLI is
|
|
59
|
+
// missing (#1061 review).
|
|
60
|
+
if (err instanceof ClaudeCliNotFoundError) throw err;
|
|
61
|
+
log.warn("memory", "llm classifier: summarize threw", {
|
|
62
|
+
preview: candidate.body.slice(0, 80),
|
|
63
|
+
error: errorMessage(err),
|
|
64
|
+
});
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return parseClassifierVerdict(raw);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildUserPrompt(candidate: MemoryCandidate): string {
|
|
72
|
+
const sectionLine = candidate.section ? `Section header: ${candidate.section}` : "Section header: (none)";
|
|
73
|
+
return [sectionLine, `Bullet: ${candidate.body}`, "", "Output JSON only."].join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Tolerant JSON extraction — Claude occasionally wraps the verdict in
|
|
77
|
+
// a code fence or adds a leading word despite the prompt ban. Strip
|
|
78
|
+
// surrounding fences and pick out the first `{...}` block (or a bare
|
|
79
|
+
// `null`) before parsing.
|
|
80
|
+
export function parseClassifierVerdict(raw: string): MemoryClassification | null {
|
|
81
|
+
const trimmed = stripFenceAndWhitespace(raw);
|
|
82
|
+
if (trimmed === "null") return null;
|
|
83
|
+
const objectText = extractFirstObject(trimmed);
|
|
84
|
+
if (!objectText) return null;
|
|
85
|
+
let parsed: unknown;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(objectText);
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (!isPlainObject(parsed)) return null;
|
|
92
|
+
const { type, description } = parsed as { type?: unknown; description?: unknown };
|
|
93
|
+
if (!isMemoryType(type)) return null;
|
|
94
|
+
const desc = typeof description === "string" ? description.trim() : "";
|
|
95
|
+
// The description is optional from the spec's perspective —
|
|
96
|
+
// migrate.ts falls back to a body-derived description when missing.
|
|
97
|
+
// But if present, normalise to a single line and cap length.
|
|
98
|
+
// Cap matches the prompt's `<=100 chars>` declaration so persisted
|
|
99
|
+
// entries never exceed the schema the classifier was told to follow
|
|
100
|
+
// (#1061 review).
|
|
101
|
+
return desc.length > 0 ? { type: type as MemoryType, description: oneLine(desc).slice(0, 100) } : { type: type as MemoryType };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function stripFenceAndWhitespace(raw: string): string {
|
|
105
|
+
let text = raw.trim();
|
|
106
|
+
if (text.startsWith("```")) {
|
|
107
|
+
const firstNl = text.indexOf("\n");
|
|
108
|
+
if (firstNl >= 0) text = text.slice(firstNl + 1);
|
|
109
|
+
if (text.endsWith("```")) text = text.slice(0, -3);
|
|
110
|
+
}
|
|
111
|
+
return text.trim();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function extractFirstObject(text: string): string | null {
|
|
115
|
+
const start = text.indexOf("{");
|
|
116
|
+
if (start < 0) return null;
|
|
117
|
+
let depth = 0;
|
|
118
|
+
let index = start;
|
|
119
|
+
while (index < text.length) {
|
|
120
|
+
const char = text[index];
|
|
121
|
+
if (char === '"') {
|
|
122
|
+
index = skipStringBody(text, index + 1);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (char === "{") depth += 1;
|
|
126
|
+
else if (char === "}") {
|
|
127
|
+
depth -= 1;
|
|
128
|
+
if (depth === 0) return text.slice(start, index + 1);
|
|
129
|
+
}
|
|
130
|
+
index += 1;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Returns the index immediately after the closing `"`, or `text.length`
|
|
136
|
+
// if the string is unterminated. Backslash escapes the next char so
|
|
137
|
+
// `\"` does not close the string.
|
|
138
|
+
function skipStringBody(text: string, fromIndex: number): number {
|
|
139
|
+
let index = fromIndex;
|
|
140
|
+
while (index < text.length) {
|
|
141
|
+
const char = text[index];
|
|
142
|
+
if (char === "\\") {
|
|
143
|
+
index += 2;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (char === '"') return index + 1;
|
|
147
|
+
index += 1;
|
|
148
|
+
}
|
|
149
|
+
return text.length;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
153
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function oneLine(text: string): string {
|
|
157
|
+
return text.replace(/\s+/g, " ").trim();
|
|
158
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Migration of legacy `conversations/memory.md` into the typed
|
|
2
|
+
// directory layout (#1029 PR-A). This module is a library — it does
|
|
3
|
+
// NOT decide when to run. PR-B wires it into `workspace.ts`'s init
|
|
4
|
+
// path so the conversion happens once on first start after the new
|
|
5
|
+
// layout ships.
|
|
6
|
+
//
|
|
7
|
+
// CLEANUP 2026-07-01: see `run.ts` — this file is part of the
|
|
8
|
+
// one-shot `memory.md` → atomic migration chain and goes when the
|
|
9
|
+
// chain goes.
|
|
10
|
+
//
|
|
11
|
+
// Steps:
|
|
12
|
+
// 1. Read existing `conversations/memory.md`. Absent → no-op.
|
|
13
|
+
// 2. Split by `## ` (H2). Each H2 block becomes a candidate entry
|
|
14
|
+
// (one per top-level bullet).
|
|
15
|
+
// 3. Ask the supplied classifier "preference / interest / fact /
|
|
16
|
+
// reference?" per candidate. Null verdict → skip + count.
|
|
17
|
+
// 4. Write each kept candidate as `<type>_<slug>.md`.
|
|
18
|
+
// 5. Rebuild `MEMORY.md`.
|
|
19
|
+
// 6. Rename source to `memory.md.backup` (never deleted).
|
|
20
|
+
|
|
21
|
+
import { mkdir, rename } from "node:fs/promises";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
|
|
24
|
+
import { writeFileAtomic } from "../../utils/files/atomic.js";
|
|
25
|
+
import { readTextSafe } from "../../utils/files/safe.js";
|
|
26
|
+
import { log } from "../../system/logger/index.js";
|
|
27
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
28
|
+
import { ClaudeCliNotFoundError } from "../journal/archivist-cli.js";
|
|
29
|
+
import { WORKSPACE_FILES } from "../paths.js";
|
|
30
|
+
import { regenerateIndex, writeMemoryEntry } from "./io.js";
|
|
31
|
+
import { isMemoryType, slugifyMemoryName, type MemoryEntry, type MemoryType } from "./types.js";
|
|
32
|
+
|
|
33
|
+
export interface MemoryClassification {
|
|
34
|
+
type: MemoryType;
|
|
35
|
+
/** Optional one-line description. Falls back to a truncated body. */
|
|
36
|
+
description?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type MemoryClassifier = (candidate: MemoryCandidate) => Promise<MemoryClassification | null>;
|
|
40
|
+
|
|
41
|
+
export interface MemoryCandidate {
|
|
42
|
+
/** H2 section header the bullet was found under. Empty string for
|
|
43
|
+
* bullets that appeared before any H2. */
|
|
44
|
+
section: string;
|
|
45
|
+
/** Single-line text of the bullet (lead-in `- ` already stripped). */
|
|
46
|
+
body: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MigrationResult {
|
|
50
|
+
/** Set when there was nothing to migrate (no `memory.md`). */
|
|
51
|
+
noop: boolean;
|
|
52
|
+
/** Count of candidates accepted, broken down by type. */
|
|
53
|
+
written: Record<MemoryType, number>;
|
|
54
|
+
/** Candidates the classifier returned `null` for. */
|
|
55
|
+
skippedByClassifier: number;
|
|
56
|
+
/** Candidates that errored on write — caller may want to retry or
|
|
57
|
+
* surface to the user. The migration continues past write errors
|
|
58
|
+
* so a single bad entry doesn't strand the whole batch. */
|
|
59
|
+
writeErrors: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Number of in-flight `classify` calls. Each call spawns the Claude
|
|
63
|
+
// CLI, so there's a real cost to going wide; 4 is empirically a
|
|
64
|
+
// sweet spot — well above the 1-at-a-time baseline (~10× faster on
|
|
65
|
+
// real legacy files) while staying polite to the user's machine and
|
|
66
|
+
// to Claude's quota. The classifier callback is the only awaited
|
|
67
|
+
// step, so this is the dominant knob.
|
|
68
|
+
const CLASSIFY_CONCURRENCY = 4;
|
|
69
|
+
|
|
70
|
+
// Progress logs emitted every N processed candidates. A long legacy
|
|
71
|
+
// file (50+ bullets) takes minutes; without periodic logging it
|
|
72
|
+
// looks frozen.
|
|
73
|
+
const PROGRESS_LOG_INTERVAL = 5;
|
|
74
|
+
|
|
75
|
+
export async function migrateLegacyMemory(workspaceRoot: string, classify: MemoryClassifier): Promise<MigrationResult> {
|
|
76
|
+
const sourcePath = path.join(workspaceRoot, WORKSPACE_FILES.memory);
|
|
77
|
+
const raw = await readTextSafe(sourcePath);
|
|
78
|
+
if (raw === null) {
|
|
79
|
+
return emptyResult(true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const candidates = parseCandidates(raw);
|
|
83
|
+
log.info("memory", "migration: classifying candidates", {
|
|
84
|
+
candidateCount: candidates.length,
|
|
85
|
+
concurrency: CLASSIFY_CONCURRENCY,
|
|
86
|
+
});
|
|
87
|
+
const classifications = await classifyInParallel(classify, candidates);
|
|
88
|
+
|
|
89
|
+
const result = emptyResult(false);
|
|
90
|
+
const usedSlugs = new Set<string>();
|
|
91
|
+
|
|
92
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
93
|
+
const classification = classifications[index];
|
|
94
|
+
const candidate = candidates[index];
|
|
95
|
+
if (!classification) {
|
|
96
|
+
result.skippedByClassifier += 1;
|
|
97
|
+
} else {
|
|
98
|
+
const entry = buildEntry(candidate, classification, usedSlugs);
|
|
99
|
+
try {
|
|
100
|
+
await writeMemoryEntry(workspaceRoot, entry);
|
|
101
|
+
usedSlugs.add(entry.slug);
|
|
102
|
+
result.written[entry.type] += 1;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
log.warn("memory", "migration: write failed", {
|
|
105
|
+
slug: entry.slug,
|
|
106
|
+
error: errorMessage(err),
|
|
107
|
+
});
|
|
108
|
+
result.writeErrors += 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const processed = index + 1;
|
|
112
|
+
if (processed % PROGRESS_LOG_INTERVAL === 0 || processed === candidates.length) {
|
|
113
|
+
log.info("memory", "migration: progress", {
|
|
114
|
+
processed,
|
|
115
|
+
total: candidates.length,
|
|
116
|
+
written: result.written,
|
|
117
|
+
skippedByClassifier: result.skippedByClassifier,
|
|
118
|
+
writeErrors: result.writeErrors,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await regenerateIndex(workspaceRoot);
|
|
124
|
+
await renameToBackup(sourcePath);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Classify candidates with bounded concurrency. Workers pull
|
|
129
|
+
// indices off a shared cursor and write into a pre-allocated array
|
|
130
|
+
// so `result[i]` matches `candidates[i]`. Each await falls inside
|
|
131
|
+
// `safeClassify`, which already swallows individual classifier
|
|
132
|
+
// throws (logging once); a concurrent failure can therefore not
|
|
133
|
+
// poison the rest of the batch.
|
|
134
|
+
async function classifyInParallel(classify: MemoryClassifier, candidates: readonly MemoryCandidate[]): Promise<(MemoryClassification | null)[]> {
|
|
135
|
+
const results: (MemoryClassification | null)[] = new Array(candidates.length).fill(null);
|
|
136
|
+
let nextIndex = 0;
|
|
137
|
+
const worker = async (): Promise<void> => {
|
|
138
|
+
while (true) {
|
|
139
|
+
const index = nextIndex;
|
|
140
|
+
nextIndex += 1;
|
|
141
|
+
if (index >= candidates.length) return;
|
|
142
|
+
results[index] = await safeClassify(classify, candidates[index]);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const workers = Array.from({ length: Math.min(CLASSIFY_CONCURRENCY, candidates.length) }, () => worker());
|
|
146
|
+
await Promise.all(workers);
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function emptyResult(noop: boolean): MigrationResult {
|
|
151
|
+
return {
|
|
152
|
+
noop,
|
|
153
|
+
written: { preference: 0, interest: 0, fact: 0, reference: 0 },
|
|
154
|
+
skippedByClassifier: 0,
|
|
155
|
+
writeErrors: 0,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Split the legacy file into candidate bullets. We accept either
|
|
160
|
+
// `- ` or `* ` as bullet markers and ignore continuation lines (a
|
|
161
|
+
// candidate is one bullet line). The H2 header — when present —
|
|
162
|
+
// rides along as `section` so the classifier can use it as context.
|
|
163
|
+
// Parsing is character-driven (no regex) to keep sonar happy and to
|
|
164
|
+
// make the bullet-vs-header classification obvious from the code.
|
|
165
|
+
function parseCandidates(raw: string): MemoryCandidate[] {
|
|
166
|
+
const lines = raw.split("\n");
|
|
167
|
+
let section = "";
|
|
168
|
+
const out: MemoryCandidate[] = [];
|
|
169
|
+
for (const lineRaw of lines) {
|
|
170
|
+
const line = stripCarriageReturn(lineRaw);
|
|
171
|
+
const header = extractHeader(line);
|
|
172
|
+
if (header !== null) {
|
|
173
|
+
section = header;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const bullet = extractBullet(line);
|
|
177
|
+
if (bullet) out.push({ section, body: bullet });
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function stripCarriageReturn(line: string): string {
|
|
183
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function extractHeader(line: string): string | null {
|
|
187
|
+
if (!line.startsWith("## ")) return null;
|
|
188
|
+
return line.slice(3).trim();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function extractBullet(line: string): string | null {
|
|
192
|
+
const trimmedLeft = line.replace(/^[\t ]+/, "");
|
|
193
|
+
if (!trimmedLeft.startsWith("- ") && !trimmedLeft.startsWith("* ")) return null;
|
|
194
|
+
const body = trimmedLeft.slice(2).trim();
|
|
195
|
+
return body.length > 0 ? body : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function safeClassify(classify: MemoryClassifier, candidate: MemoryCandidate): Promise<MemoryClassification | null> {
|
|
199
|
+
try {
|
|
200
|
+
const verdict = await classify(candidate);
|
|
201
|
+
if (verdict && isMemoryType(verdict.type)) return verdict;
|
|
202
|
+
return null;
|
|
203
|
+
} catch (err) {
|
|
204
|
+
// Startup-wide failures (CLI missing) MUST escape so
|
|
205
|
+
// `runMemoryMigrationOnce` can log once + leave the legacy file
|
|
206
|
+
// for retry. Without this, every candidate would silently degrade
|
|
207
|
+
// to `null` and look like normal classifier disagreement
|
|
208
|
+
// (#1061 review).
|
|
209
|
+
if (err instanceof ClaudeCliNotFoundError) throw err;
|
|
210
|
+
log.warn("memory", "migration: classifier threw", {
|
|
211
|
+
preview: candidate.body.slice(0, 80),
|
|
212
|
+
error: errorMessage(err),
|
|
213
|
+
});
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildEntry(candidate: MemoryCandidate, classification: MemoryClassification, usedSlugs: Set<string>): MemoryEntry {
|
|
219
|
+
const name = candidate.body.length > 80 ? `${candidate.body.slice(0, 77)}…` : candidate.body;
|
|
220
|
+
const description = classification.description?.trim() || truncateForDescription(candidate.body);
|
|
221
|
+
const baseSlug = slugifyMemoryName(candidate.body, classification.type);
|
|
222
|
+
const slug = uniqueSlug(baseSlug, usedSlugs);
|
|
223
|
+
return {
|
|
224
|
+
name,
|
|
225
|
+
description,
|
|
226
|
+
type: classification.type,
|
|
227
|
+
body: candidate.body,
|
|
228
|
+
slug,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function truncateForDescription(text: string): string {
|
|
233
|
+
const oneLine = text.replace(/\s+/g, " ").trim();
|
|
234
|
+
return oneLine.length > 120 ? `${oneLine.slice(0, 117)}…` : oneLine;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function uniqueSlug(base: string, used: Set<string>): string {
|
|
238
|
+
if (!used.has(base)) return base;
|
|
239
|
+
let counter = 2;
|
|
240
|
+
while (used.has(`${base}-${counter}`)) counter += 1;
|
|
241
|
+
return `${base}-${counter}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function renameToBackup(sourcePath: string): Promise<void> {
|
|
245
|
+
const backupPath = `${sourcePath}.backup`;
|
|
246
|
+
try {
|
|
247
|
+
await rename(sourcePath, backupPath);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
log.warn("memory", "migration: backup rename failed", {
|
|
250
|
+
sourcePath,
|
|
251
|
+
error: errorMessage(err),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Bare write of the source file used in tests for setup. Production
|
|
257
|
+
// callers don't need this — `memory.md` is created by user activity
|
|
258
|
+
// or by an older mulmoclaude version.
|
|
259
|
+
export async function writeLegacyMemoryForTest(workspaceRoot: string, content: string): Promise<void> {
|
|
260
|
+
const sourcePath = path.join(workspaceRoot, WORKSPACE_FILES.memory);
|
|
261
|
+
await mkdir(path.dirname(sourcePath), { recursive: true });
|
|
262
|
+
await writeFileAtomic(sourcePath, content);
|
|
263
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// One-shot legacy-memory migration entry point used by server
|
|
2
|
+
// startup (#1029 PR-B). Idempotent: returns immediately when there
|
|
3
|
+
// is nothing to do (legacy file absent or already migrated).
|
|
4
|
+
//
|
|
5
|
+
// Concurrency: the agent may serve requests before this finishes.
|
|
6
|
+
// The brief race window is documented in
|
|
7
|
+
// `plans/done/feat-memory-storage-wire.md`.
|
|
8
|
+
//
|
|
9
|
+
// CLEANUP 2026-07-01: this is one-shot migration code for the
|
|
10
|
+
// `memory.md` → atomic transition (#1029). After every active
|
|
11
|
+
// workspace has run through both `runMemoryMigrationOnce` and the
|
|
12
|
+
// follow-on `runTopicMigrationOnce`, this file plus the rest of
|
|
13
|
+
// the migration chain (`migrate.ts`, `llm-classifier.ts`, the
|
|
14
|
+
// atomic-aware branches in `prompt.ts`, the chain in
|
|
15
|
+
// `server/index.ts`) can be deleted in one sweep. Workspaces that
|
|
16
|
+
// have not migrated by then will need a manual conversion path —
|
|
17
|
+
// document the recovery before removing the code.
|
|
18
|
+
|
|
19
|
+
import { existsSync, statSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
|
|
22
|
+
import { runClaudeCli, ClaudeCliNotFoundError, type Summarize } from "../journal/archivist-cli.js";
|
|
23
|
+
import { makeLlmMemoryClassifier } from "./llm-classifier.js";
|
|
24
|
+
import { migrateLegacyMemory } from "./migrate.js";
|
|
25
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
26
|
+
import { log } from "../../system/logger/index.js";
|
|
27
|
+
|
|
28
|
+
export interface RunMigrationDeps {
|
|
29
|
+
/** Override the summarize callback (useful for tests). Defaults to
|
|
30
|
+
* the production `runClaudeCli` which spawns the Claude CLI. */
|
|
31
|
+
summarize?: Summarize;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function runMemoryMigrationOnce(workspaceRoot: string, deps: RunMigrationDeps = {}): Promise<void> {
|
|
35
|
+
const legacyPath = path.join(workspaceRoot, "conversations", "memory.md");
|
|
36
|
+
if (!existsSync(legacyPath)) {
|
|
37
|
+
log.debug("memory", "migration: no legacy file, skipping");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// If the file is suspiciously empty (just the placeholder we used
|
|
41
|
+
// to write at init), skip — the migration would yield zero entries
|
|
42
|
+
// and the rename to `.backup` is pointless.
|
|
43
|
+
const stat = statSync(legacyPath);
|
|
44
|
+
if (stat.size < 64) {
|
|
45
|
+
log.info("memory", "migration: legacy file is below the placeholder threshold, skipping");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// The rename to `.backup` is the final step of `migrateLegacyMemory`,
|
|
49
|
+
// so its presence is the "migration completed" marker. A workspace
|
|
50
|
+
// where both `memory.md` AND `memory.md.backup` exist means the
|
|
51
|
+
// user re-introduced the legacy file after a previous successful
|
|
52
|
+
// migration (probably by mistake or by extracting the backup).
|
|
53
|
+
// Re-running here would re-classify the bullets and could clobber
|
|
54
|
+
// typed entries the user has been editing in place; skip with a
|
|
55
|
+
// clear log instead. The interrupted-migration retry case still
|
|
56
|
+
// works because no `.backup` exists in that state — the rename
|
|
57
|
+
// never ran.
|
|
58
|
+
const backupPath = `${legacyPath}.backup`;
|
|
59
|
+
if (existsSync(backupPath)) {
|
|
60
|
+
log.info("memory", "migration: legacy file present but .backup also exists — refusing to re-run", {
|
|
61
|
+
legacyPath,
|
|
62
|
+
backupPath,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const summarize = deps.summarize ?? runClaudeCli;
|
|
67
|
+
const classifier = makeLlmMemoryClassifier({ summarize });
|
|
68
|
+
log.info("memory", "migration: starting", { legacyPath });
|
|
69
|
+
try {
|
|
70
|
+
const result = await migrateLegacyMemory(workspaceRoot, classifier);
|
|
71
|
+
log.info("memory", "migration: done", {
|
|
72
|
+
noop: result.noop,
|
|
73
|
+
written: result.written,
|
|
74
|
+
skippedByClassifier: result.skippedByClassifier,
|
|
75
|
+
writeErrors: result.writeErrors,
|
|
76
|
+
});
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err instanceof ClaudeCliNotFoundError) {
|
|
79
|
+
log.warn("memory", "migration: claude CLI not on PATH; legacy memory left in place");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
log.error("memory", "migration: threw, legacy memory left in place", { error: errorMessage(err) });
|
|
83
|
+
}
|
|
84
|
+
}
|