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,142 @@
|
|
|
1
|
+
// Topic-based memory schema (#1070). Each file groups related
|
|
2
|
+
// bullets under a single topic. The frontmatter carries the type
|
|
3
|
+
// and topic name; the body is markdown (H1 + optional H2 sections +
|
|
4
|
+
// bullets). Coexists with #1029's atomic files during the
|
|
5
|
+
// transition — PR-B retires the atomic format.
|
|
6
|
+
|
|
7
|
+
import type { MemoryType } from "./types.js";
|
|
8
|
+
|
|
9
|
+
export interface TopicMemoryFile {
|
|
10
|
+
type: MemoryType;
|
|
11
|
+
/** Filename without `.md`. Stable identifier; the index links to it. */
|
|
12
|
+
topic: string;
|
|
13
|
+
/** Raw markdown body — H1 heading + H2 subsections + bullets. */
|
|
14
|
+
body: string;
|
|
15
|
+
/** H2 headings extracted from the body, in source order, with
|
|
16
|
+
* whitespace trimmed. The index renders these as the file's
|
|
17
|
+
* "tags". Empty array when the file has no H2 yet (a young
|
|
18
|
+
* topic with only bullets directly under H1). */
|
|
19
|
+
sections: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Pull H2 headings out of the body. Pure / regex-free / iterable so
|
|
23
|
+
// a long body can't trip sonarjs. `##` followed by space OR tab is
|
|
24
|
+
// accepted so a markdown file authored with mixed indentation
|
|
25
|
+
// doesn't silently lose tags. `###` and deeper are NOT treated as
|
|
26
|
+
// tags so the file can use them for further structure without
|
|
27
|
+
// leaking into the index.
|
|
28
|
+
export function extractH2Sections(body: string): string[] {
|
|
29
|
+
const sections: string[] = [];
|
|
30
|
+
for (const lineRaw of body.split("\n")) {
|
|
31
|
+
const line = stripCarriageReturn(lineRaw);
|
|
32
|
+
if (!isH2Line(line)) continue;
|
|
33
|
+
const heading = line.slice(2).trim();
|
|
34
|
+
if (heading.length > 0) sections.push(heading);
|
|
35
|
+
}
|
|
36
|
+
return sections;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isH2Line(line: string): boolean {
|
|
40
|
+
if (!line.startsWith("##")) return false;
|
|
41
|
+
if (line.startsWith("###")) return false;
|
|
42
|
+
if (line.length === 2) return false;
|
|
43
|
+
const [, , next] = line;
|
|
44
|
+
return next === " " || next === "\t";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function stripCarriageReturn(line: string): string {
|
|
48
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Maximum length of a topic slug (filename without `.md`). Both the
|
|
52
|
+
// slugifier and the safety gate honour this cap, and downstream
|
|
53
|
+
// callers that suffix a slug (e.g. collision resolution in
|
|
54
|
+
// `topic-migrate`'s `pickUniqueSlug`) must trim the base so the
|
|
55
|
+
// suffixed result still fits.
|
|
56
|
+
export const MAX_TOPIC_SLUG_LENGTH = 60;
|
|
57
|
+
|
|
58
|
+
// Windows refuses to create files with these basenames even with an
|
|
59
|
+
// extension (`con.md` is blocked). Without this gate a clusterer
|
|
60
|
+
// that returns "CON" would slugify to `con`, pass `isSafeTopicSlug`,
|
|
61
|
+
// and then fail migration on Windows. Listed in lowercase since the
|
|
62
|
+
// slugifier lowercases first.
|
|
63
|
+
const WINDOWS_RESERVED_BASENAMES = new Set([
|
|
64
|
+
"con",
|
|
65
|
+
"prn",
|
|
66
|
+
"aux",
|
|
67
|
+
"nul",
|
|
68
|
+
"com1",
|
|
69
|
+
"com2",
|
|
70
|
+
"com3",
|
|
71
|
+
"com4",
|
|
72
|
+
"com5",
|
|
73
|
+
"com6",
|
|
74
|
+
"com7",
|
|
75
|
+
"com8",
|
|
76
|
+
"com9",
|
|
77
|
+
"lpt1",
|
|
78
|
+
"lpt2",
|
|
79
|
+
"lpt3",
|
|
80
|
+
"lpt4",
|
|
81
|
+
"lpt5",
|
|
82
|
+
"lpt6",
|
|
83
|
+
"lpt7",
|
|
84
|
+
"lpt8",
|
|
85
|
+
"lpt9",
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Slugify a topic name for use as a filename. `<type>/<topic>.md`
|
|
89
|
+
// must keep `topic` filesystem-safe and short. Collapses anything
|
|
90
|
+
// non-alnum into a single `-`, lowercases, trims trailing
|
|
91
|
+
// separators, caps at MAX_TOPIC_SLUG_LENGTH chars. Returns null when
|
|
92
|
+
// the result is empty (caller decides whether to fall back to a
|
|
93
|
+
// hash).
|
|
94
|
+
export function slugifyTopicName(name: string): string | null {
|
|
95
|
+
const lower = name.toLowerCase();
|
|
96
|
+
const out: string[] = [];
|
|
97
|
+
let lastWasSep = true;
|
|
98
|
+
for (const char of lower) {
|
|
99
|
+
if ((char >= "a" && char <= "z") || (char >= "0" && char <= "9")) {
|
|
100
|
+
out.push(char);
|
|
101
|
+
lastWasSep = false;
|
|
102
|
+
} else if (!lastWasSep) {
|
|
103
|
+
out.push("-");
|
|
104
|
+
lastWasSep = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
while (out.length > 0 && out[out.length - 1] === "-") out.pop();
|
|
108
|
+
const compact = out.slice(0, MAX_TOPIC_SLUG_LENGTH).join("");
|
|
109
|
+
if (compact.length === 0) return null;
|
|
110
|
+
const trimmed = trimTrailing(compact, "-");
|
|
111
|
+
if (WINDOWS_RESERVED_BASENAMES.has(trimmed)) return null;
|
|
112
|
+
return trimmed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function trimTrailing(text: string, char: string): string {
|
|
116
|
+
let end = text.length;
|
|
117
|
+
while (end > 0 && text[end - 1] === char) end -= 1;
|
|
118
|
+
return text.slice(0, end);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Strict shape gate: lowercase alnum + `-` only, length 1–60, no
|
|
122
|
+
// leading / trailing `-`, not the reserved index name. The
|
|
123
|
+
// strictness is intentional: a clusterer's first attempt at a topic
|
|
124
|
+
// slug is often a free-form phrase ("AI Research Papers!"), and we
|
|
125
|
+
// want such inputs to fall through to `slugifyTopicName` rather
|
|
126
|
+
// than land verbatim on the filesystem with spaces / punctuation
|
|
127
|
+
// baked in. `isSafeMemorySlug` from #1029 was looser to allow
|
|
128
|
+
// unicode body suffixes; here we force the slug format to be
|
|
129
|
+
// filename-friendly so the topic doubles as the path component.
|
|
130
|
+
export function isSafeTopicSlug(slug: string): boolean {
|
|
131
|
+
if (typeof slug !== "string") return false;
|
|
132
|
+
if (slug.length === 0) return false;
|
|
133
|
+
if (slug.length > MAX_TOPIC_SLUG_LENGTH) return false;
|
|
134
|
+
if (slug.startsWith("-") || slug.endsWith("-")) return false;
|
|
135
|
+
for (const char of slug) {
|
|
136
|
+
const ok = (char >= "a" && char <= "z") || (char >= "0" && char <= "9") || char === "-";
|
|
137
|
+
if (!ok) return false;
|
|
138
|
+
}
|
|
139
|
+
if (slug === "memory") return false;
|
|
140
|
+
if (WINDOWS_RESERVED_BASENAMES.has(slug)) return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Typed memory schema (#1029). Each entry is a markdown file with a
|
|
2
|
+
// YAML frontmatter envelope. The directory layout, filename
|
|
3
|
+
// convention, and field set are documented in
|
|
4
|
+
// `plans/done/feat-memory-storage-utilities.md`.
|
|
5
|
+
//
|
|
6
|
+
// `type` is the source of truth for an entry's classification —
|
|
7
|
+
// filenames follow `<type>_<slug>.md` for ergonomics but the reader
|
|
8
|
+
// must trust the frontmatter, never the filename.
|
|
9
|
+
|
|
10
|
+
export const MEMORY_TYPES = ["preference", "interest", "fact", "reference"] as const;
|
|
11
|
+
|
|
12
|
+
export type MemoryType = (typeof MEMORY_TYPES)[number];
|
|
13
|
+
|
|
14
|
+
export interface MemoryEntry {
|
|
15
|
+
/** One-line human-readable label. Becomes the link text in MEMORY.md. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Short blurb shown after the link in MEMORY.md and used as the
|
|
18
|
+
* description hint when the agent decides whether to read the
|
|
19
|
+
* full entry. */
|
|
20
|
+
description: string;
|
|
21
|
+
type: MemoryType;
|
|
22
|
+
/** Markdown body. The frontmatter envelope is stripped on parse and
|
|
23
|
+
* re-applied on write. */
|
|
24
|
+
body: string;
|
|
25
|
+
/** Filename without extension. Stable identifier used by the index
|
|
26
|
+
* link target. */
|
|
27
|
+
slug: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isMemoryType(value: unknown): value is MemoryType {
|
|
31
|
+
return typeof value === "string" && (MEMORY_TYPES as readonly string[]).includes(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Bound the alphanumeric portion of a slug. Long bullets in the
|
|
35
|
+
// legacy memory.md (e.g. paragraph-length recurring task descriptions)
|
|
36
|
+
// would otherwise produce slugs > 200 chars and trip
|
|
37
|
+
// `isSafeMemorySlug`'s upper bound, dropping the entry on disk. 80
|
|
38
|
+
// chars keeps the filename readable in `ls` output, leaves headroom
|
|
39
|
+
// under the safety cap, and is well under the 255-byte filename limit
|
|
40
|
+
// every supported filesystem honors.
|
|
41
|
+
const MAX_SLUG_BODY = 80;
|
|
42
|
+
|
|
43
|
+
// Slugify a name into a filename-safe token. ASCII-only conversion of
|
|
44
|
+
// [a-zA-Z0-9] segments; everything else collapses into a single `-`.
|
|
45
|
+
// The result is truncated to MAX_SLUG_BODY and any trailing `-` from
|
|
46
|
+
// the truncation is trimmed so the slug doesn't read like a partial
|
|
47
|
+
// word. Non-ASCII (Japanese / 中文) input falls back to the type
|
|
48
|
+
// prefix + short hash so two entries with all-non-ASCII names don't
|
|
49
|
+
// collide on the empty string. Uses an explicit char loop instead of
|
|
50
|
+
// regex so a deeply-recursive name can't trigger pathological
|
|
51
|
+
// backtracking.
|
|
52
|
+
export function slugifyMemoryName(name: string, type: MemoryType): string {
|
|
53
|
+
const ascii = compactAlnum(name.toLowerCase());
|
|
54
|
+
const bounded = trimTrailingSeparators(ascii.slice(0, MAX_SLUG_BODY));
|
|
55
|
+
if (bounded.length > 0) return `${type}_${bounded}`;
|
|
56
|
+
let hash = 0;
|
|
57
|
+
for (let index = 0; index < name.length; index += 1) {
|
|
58
|
+
hash = (hash * 31 + name.charCodeAt(index)) >>> 0;
|
|
59
|
+
}
|
|
60
|
+
return `${type}_${hash.toString(36)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function trimTrailingSeparators(text: string): string {
|
|
64
|
+
let end = text.length;
|
|
65
|
+
while (end > 0 && text[end - 1] === "-") end -= 1;
|
|
66
|
+
return text.slice(0, end);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function compactAlnum(text: string): string {
|
|
70
|
+
const out: string[] = [];
|
|
71
|
+
let lastWasSep = true;
|
|
72
|
+
for (const char of text) {
|
|
73
|
+
if ((char >= "a" && char <= "z") || (char >= "0" && char <= "9")) {
|
|
74
|
+
out.push(char);
|
|
75
|
+
lastWasSep = false;
|
|
76
|
+
} else if (!lastWasSep) {
|
|
77
|
+
out.push("-");
|
|
78
|
+
lastWasSep = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
while (out.length > 0 && out[out.length - 1] === "-") out.pop();
|
|
82
|
+
return out.join("");
|
|
83
|
+
}
|
|
@@ -83,8 +83,8 @@ export function extractDailyJsonIndex(markdown: string): NewsItem[] | null {
|
|
|
83
83
|
if (!Array.isArray(shape.items)) return null;
|
|
84
84
|
// Defensive filter: drop entries that don't carry the fields the
|
|
85
85
|
// viewer relies on.
|
|
86
|
-
return shape.items.filter(
|
|
87
|
-
|
|
86
|
+
return shape.items.filter(
|
|
87
|
+
(item): item is NewsItem =>
|
|
88
88
|
typeof item === "object" &&
|
|
89
89
|
item !== null &&
|
|
90
90
|
typeof item.id === "string" &&
|
|
@@ -92,9 +92,8 @@ export function extractDailyJsonIndex(markdown: string): NewsItem[] | null {
|
|
|
92
92
|
typeof item.url === "string" &&
|
|
93
93
|
typeof item.publishedAt === "string" &&
|
|
94
94
|
typeof item.sourceSlug === "string" &&
|
|
95
|
-
Array.isArray(item.categories)
|
|
96
|
-
|
|
97
|
-
});
|
|
95
|
+
Array.isArray(item.categories),
|
|
96
|
+
);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
// Build the list of YYYY-MM-DD strings for the last `days` days
|
|
@@ -24,6 +24,48 @@
|
|
|
24
24
|
import { homedir } from "os";
|
|
25
25
|
import path from "path";
|
|
26
26
|
|
|
27
|
+
// Well-known individual files — imported from the shared
|
|
28
|
+
// src/config/workspacePaths.ts (single source of truth for both
|
|
29
|
+
// server and frontend). Re-exported so server callers keep the
|
|
30
|
+
// same `import { WORKSPACE_FILES } from "./paths.js"` they use.
|
|
31
|
+
import { WORKSPACE_FILES } from "../../src/config/workspacePaths.js";
|
|
32
|
+
|
|
33
|
+
// Plugin-owned workspace dirs are auto-aggregated from every
|
|
34
|
+
// plugin's META in `src/plugins/metas.ts`. Adding a new plugin =
|
|
35
|
+
// register its META there; this file keeps the central
|
|
36
|
+
// `WORKSPACE_DIRS.<key>` shape via spread so existing consumers
|
|
37
|
+
// don't migrate. Plugin-specific literals never appear here.
|
|
38
|
+
import {
|
|
39
|
+
BUILT_IN_PLUGIN_METAS,
|
|
40
|
+
defineHostAggregate,
|
|
41
|
+
type BuiltInPluginMetas,
|
|
42
|
+
type HostPluginCollision,
|
|
43
|
+
type IntraPluginCollision,
|
|
44
|
+
} from "../../src/plugins/metas.js";
|
|
45
|
+
|
|
46
|
+
// Merge every plugin's `workspaceDirs` into one record. The mapped
|
|
47
|
+
// type below preserves each plugin's literal path strings (e.g.
|
|
48
|
+
// `"data/accounting"`) so consumers like `WORKSPACE_DIRS.accounting`
|
|
49
|
+
// keep their narrow types — without it, TypeScript widens to
|
|
50
|
+
// `string` and downstream `WORKSPACE_PATHS.accounting` lookups break.
|
|
51
|
+
//
|
|
52
|
+
// Distributive conditional types collapse the per-plugin union into
|
|
53
|
+
// an INTERSECTION so consumers see the merged shape rather than a
|
|
54
|
+
// union (which TS won't let you index into safely once 2+ plugins
|
|
55
|
+
// register).
|
|
56
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
57
|
+
|
|
58
|
+
// Plugins WITHOUT `workspaceDirs` must contribute an empty object
|
|
59
|
+
// (`Record<never, never>`), NOT `Record<string, never>`. The latter
|
|
60
|
+
// carries an index signature that, once intersected with a sibling
|
|
61
|
+
// plugin's narrow contribution, drags `keyof` of the merged type
|
|
62
|
+
// back to `string`. See server/workspace/workspace.ts: indexing
|
|
63
|
+
// `WORKSPACE_PATHS[key]` over `EAGER_WORKSPACE_DIRS` requires
|
|
64
|
+
// `WorkspaceDirKey` to stay a union of literals.
|
|
65
|
+
type PluginWorkspaceDirsContribution<M> = M extends { readonly workspaceDirs: infer D } ? { readonly [K in keyof D]: D[K] } : Record<never, never>;
|
|
66
|
+
|
|
67
|
+
type PluginWorkspaceDirsMap<T extends BuiltInPluginMetas> = UnionToIntersection<PluginWorkspaceDirsContribution<T[number]>>;
|
|
68
|
+
|
|
27
69
|
// Workspace root. Hard-coded to `~/mulmoclaude` — there is no
|
|
28
70
|
// WORKSPACE_PATH env override today; changing the location
|
|
29
71
|
// requires a code edit or a symlink. Re-exported by
|
|
@@ -34,9 +76,20 @@ export const workspacePath = path.join(homedir(), "mulmoclaude");
|
|
|
34
76
|
// Workspace-relative paths. Keys are the stable code-side identifiers
|
|
35
77
|
// (e.g. `markdowns` — unchanged for call-site compatibility); values
|
|
36
78
|
// are the on-disk paths, grouped per issue #284.
|
|
37
|
-
|
|
79
|
+
const HOST_WORKSPACE_DIRS = {
|
|
38
80
|
// conversations/
|
|
39
81
|
chat: "conversations/chat",
|
|
82
|
+
// Typed memory entries (#1029). One markdown file per fact, indexed
|
|
83
|
+
// by `MEMORY.md` (= WORKSPACE_FILES.memoryIndex). Replaces the
|
|
84
|
+
// single-file `memory.md`; the legacy file is kept as
|
|
85
|
+
// `memory.md.backup` after migration.
|
|
86
|
+
memoryDir: "conversations/memory",
|
|
87
|
+
// Staging dir for the atomic→topic migration (#1070 PR-A). Cluster
|
|
88
|
+
// output lands here; the user reviews via `diff`, then `topic-swap`
|
|
89
|
+
// promotes it to `memoryDir`. The dir name is also matched verbatim
|
|
90
|
+
// by `topicStagingPath` and the swap-window detection in
|
|
91
|
+
// `topic-detect.ts`, so changes here ripple through both places.
|
|
92
|
+
memoryStaging: "conversations/memory.next",
|
|
40
93
|
summaries: "conversations/summaries",
|
|
41
94
|
// Tool-trace output for WebSearch (one .md per search, referenced
|
|
42
95
|
// from chat JSONL `contentRef`). Lives alongside chat/ so search
|
|
@@ -44,11 +97,18 @@ export const WORKSPACE_DIRS = {
|
|
|
44
97
|
searches: "conversations/searches",
|
|
45
98
|
// data/
|
|
46
99
|
wiki: "data/wiki",
|
|
47
|
-
todos:
|
|
100
|
+
// todos: removed (#1145) — todo data now lives under the plugin's
|
|
101
|
+
// own `files.data` scope at `data/plugins/%40mulmoclaude%2Ftodo-plugin/`.
|
|
48
102
|
calendar: "data/calendar",
|
|
49
103
|
contacts: "data/contacts",
|
|
50
104
|
scheduler: "data/scheduler",
|
|
51
105
|
sources: "data/sources",
|
|
106
|
+
translation: "data/translation",
|
|
107
|
+
// Pasted/dropped chat attachments — saved at upload time so the
|
|
108
|
+
// LLM can be handed a stable workspace path instead of inline
|
|
109
|
+
// base64. Conversion artefacts (e.g. PPTX → PDF) live alongside
|
|
110
|
+
// the original under the same YYYY/MM partition.
|
|
111
|
+
attachments: "data/attachments",
|
|
52
112
|
transports: "data/transports",
|
|
53
113
|
// artifacts/
|
|
54
114
|
charts: "artifacts/charts",
|
|
@@ -71,6 +131,13 @@ export const WORKSPACE_DIRS = {
|
|
|
71
131
|
configs: "config",
|
|
72
132
|
roles: "config/roles",
|
|
73
133
|
helps: "config/helps",
|
|
134
|
+
// Project-scope Claude Code skills root — both user-authored and
|
|
135
|
+
// launcher-managed presets live here. Path is hardcoded by Claude
|
|
136
|
+
// Code's slash-command resolver (it scans `<cwd>/.claude/skills/`
|
|
137
|
+
// alongside `~/.claude/skills/`); we centralise the literal here
|
|
138
|
+
// so server code references it through `WORKSPACE_PATHS.claudeSkills`
|
|
139
|
+
// instead of inlining the string.
|
|
140
|
+
claudeSkills: ".claude/skills",
|
|
74
141
|
// Nested subdirs inside a top-level grouping. Kept here (rather
|
|
75
142
|
// than module-local constants) when multiple modules need to
|
|
76
143
|
// reference the same nested path — e.g. wiki/pages/ is used by
|
|
@@ -78,61 +145,72 @@ export const WORKSPACE_DIRS = {
|
|
|
78
145
|
// prompt hint.
|
|
79
146
|
wikiPages: "data/wiki/pages",
|
|
80
147
|
wikiSources: "data/wiki/sources",
|
|
148
|
+
// Per-page edit-history snapshots (#763 PR 2). Hidden by leading
|
|
149
|
+
// dot so a curious user listing `data/wiki/` doesn't trip over a
|
|
150
|
+
// peer directory of historical content. Each `<slug>/` underneath
|
|
151
|
+
// holds N snapshot .md files newest-first.
|
|
152
|
+
wikiHistory: "data/wiki/.history",
|
|
81
153
|
// Development — git-cloned repositories (#256).
|
|
82
154
|
github: "github",
|
|
155
|
+
// Runtime-loaded plugins (#1043 C-2). The `plugins/` directory holds
|
|
156
|
+
// user-installed npm-published plugin tarballs; `.cache/<name>/<ver>/`
|
|
157
|
+
// is the extracted-on-boot mirror. Both live under the workspace root
|
|
158
|
+
// so the install / extract artefacts persist across npx invocations.
|
|
159
|
+
plugins: "plugins",
|
|
160
|
+
pluginCache: "plugins/.cache",
|
|
161
|
+
// Per-runtime-plugin storage roots (#1110). The platform creates
|
|
162
|
+
// `<root>/<sanitized-pkg-name>/` lazily on first write. data is the
|
|
163
|
+
// backup target; config holds per-machine UI state / defaults.
|
|
164
|
+
pluginsData: "data/plugins",
|
|
165
|
+
pluginsConfig: "config/plugins",
|
|
166
|
+
notifier: "data/notifier",
|
|
83
167
|
} as const;
|
|
84
168
|
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
169
|
+
// First-write-wins host+plugin aggregate (see `defineHostAggregate`):
|
|
170
|
+
// host keys win on collision, second-claiming plugin wins are
|
|
171
|
+
// dropped, both diagnostic lists are exposed for boot warnings.
|
|
172
|
+
const WORKSPACE_DIRS_AGGREGATE = defineHostAggregate(BUILT_IN_PLUGIN_METAS, {
|
|
173
|
+
label: "WORKSPACE_DIRS",
|
|
174
|
+
hostRecord: HOST_WORKSPACE_DIRS,
|
|
175
|
+
// Reserve `WORKSPACE_FILES` keys too — those land in `WORKSPACE_PATHS`
|
|
176
|
+
// alongside dir paths and would silently overwrite a plugin's
|
|
177
|
+
// `workspaceDirs.<sameKey>` at the absolute-path step, leaving
|
|
178
|
+
// `WORKSPACE_DIRS` and `WORKSPACE_PATHS` disagreeing for that key
|
|
179
|
+
// (CR review #1125).
|
|
180
|
+
additionalReservedKeys: new Set(Object.keys(WORKSPACE_FILES)),
|
|
181
|
+
extract: (meta) => meta.workspaceDirs,
|
|
182
|
+
dimension: "workspaceDirs",
|
|
183
|
+
});
|
|
184
|
+
export const WORKSPACE_DIRS_HOST_COLLISIONS: readonly HostPluginCollision[] = WORKSPACE_DIRS_AGGREGATE.hostCollisions;
|
|
185
|
+
export const WORKSPACE_DIRS_INTRA_COLLISIONS: readonly IntraPluginCollision[] = WORKSPACE_DIRS_AGGREGATE.intraCollisions;
|
|
186
|
+
|
|
187
|
+
export const WORKSPACE_DIRS = WORKSPACE_DIRS_AGGREGATE.merged as unknown as typeof HOST_WORKSPACE_DIRS & PluginWorkspaceDirsMap<BuiltInPluginMetas>;
|
|
90
188
|
export { WORKSPACE_FILES };
|
|
91
189
|
|
|
92
190
|
// Absolute paths, built once at module load from `workspacePath`.
|
|
93
191
|
// The `workspacePath` const is itself fixed (reads `homedir()`
|
|
94
192
|
// at process start — no env override, see `server/workspace.ts`),
|
|
95
193
|
// so freezing these paths is safe.
|
|
194
|
+
//
|
|
195
|
+
// Auto-derived from `WORKSPACE_DIRS` and `WORKSPACE_FILES`. Adding
|
|
196
|
+
// a new dir or file to the upstream maps now flows into
|
|
197
|
+
// `WORKSPACE_PATHS` automatically — no second hand-curated edit
|
|
198
|
+
// required (CodeRabbit #1125 review: previously plugins adding
|
|
199
|
+
// `workspaceDirs` keys still needed a manual `WORKSPACE_PATHS`
|
|
200
|
+
// patch-up to be reachable in absolute form).
|
|
201
|
+
const WORKSPACE_DIR_PATHS = Object.fromEntries(Object.entries(WORKSPACE_DIRS).map(([key, relativePath]) => [key, path.join(workspacePath, relativePath)])) as {
|
|
202
|
+
readonly [K in keyof typeof WORKSPACE_DIRS]: string;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const WORKSPACE_FILE_PATHS = Object.fromEntries(
|
|
206
|
+
Object.entries(WORKSPACE_FILES).map(([key, relativePath]) => [key, path.join(workspacePath, relativePath)]),
|
|
207
|
+
) as {
|
|
208
|
+
readonly [K in keyof typeof WORKSPACE_FILES]: string;
|
|
209
|
+
};
|
|
210
|
+
|
|
96
211
|
export const WORKSPACE_PATHS = {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
calendar: path.join(workspacePath, WORKSPACE_DIRS.calendar),
|
|
100
|
-
contacts: path.join(workspacePath, WORKSPACE_DIRS.contacts),
|
|
101
|
-
scheduler: path.join(workspacePath, WORKSPACE_DIRS.scheduler),
|
|
102
|
-
roles: path.join(workspacePath, WORKSPACE_DIRS.roles),
|
|
103
|
-
stories: path.join(workspacePath, WORKSPACE_DIRS.stories),
|
|
104
|
-
images: path.join(workspacePath, WORKSPACE_DIRS.images),
|
|
105
|
-
markdowns: path.join(workspacePath, WORKSPACE_DIRS.markdowns),
|
|
106
|
-
spreadsheets: path.join(workspacePath, WORKSPACE_DIRS.spreadsheets),
|
|
107
|
-
charts: path.join(workspacePath, WORKSPACE_DIRS.charts),
|
|
108
|
-
configs: path.join(workspacePath, WORKSPACE_DIRS.configs),
|
|
109
|
-
helps: path.join(workspacePath, WORKSPACE_DIRS.helps),
|
|
110
|
-
wiki: path.join(workspacePath, WORKSPACE_DIRS.wiki),
|
|
111
|
-
news: path.join(workspacePath, WORKSPACE_DIRS.news),
|
|
112
|
-
sources: path.join(workspacePath, WORKSPACE_DIRS.sources),
|
|
113
|
-
summaries: path.join(workspacePath, WORKSPACE_DIRS.summaries),
|
|
114
|
-
searches: path.join(workspacePath, WORKSPACE_DIRS.searches),
|
|
115
|
-
htmls: path.join(workspacePath, WORKSPACE_DIRS.htmls),
|
|
116
|
-
html: path.join(workspacePath, WORKSPACE_DIRS.html),
|
|
117
|
-
transports: path.join(workspacePath, WORKSPACE_DIRS.transports),
|
|
118
|
-
github: path.join(workspacePath, WORKSPACE_DIRS.github),
|
|
119
|
-
// nested subdirs
|
|
120
|
-
wikiPages: path.join(workspacePath, WORKSPACE_DIRS.wikiPages),
|
|
121
|
-
wikiSources: path.join(workspacePath, WORKSPACE_DIRS.wikiSources),
|
|
122
|
-
// files
|
|
123
|
-
memory: path.join(workspacePath, WORKSPACE_FILES.memory),
|
|
124
|
-
sessionToken: path.join(workspacePath, WORKSPACE_FILES.sessionToken),
|
|
125
|
-
wikiIndex: path.join(workspacePath, WORKSPACE_FILES.wikiIndex),
|
|
126
|
-
wikiLog: path.join(workspacePath, WORKSPACE_FILES.wikiLog),
|
|
127
|
-
wikiSchema: path.join(workspacePath, WORKSPACE_FILES.wikiSchema),
|
|
128
|
-
wikiSummary: path.join(workspacePath, WORKSPACE_FILES.wikiSummary),
|
|
129
|
-
summariesIndex: path.join(workspacePath, WORKSPACE_FILES.summariesIndex),
|
|
130
|
-
todosItems: path.join(workspacePath, WORKSPACE_FILES.todosItems),
|
|
131
|
-
todosColumns: path.join(workspacePath, WORKSPACE_FILES.todosColumns),
|
|
132
|
-
schedulerItems: path.join(workspacePath, WORKSPACE_FILES.schedulerItems),
|
|
133
|
-
schedulerUserTasks: path.join(workspacePath, WORKSPACE_FILES.schedulerUserTasks),
|
|
134
|
-
schedulerOverrides: path.join(workspacePath, WORKSPACE_FILES.schedulerOverrides),
|
|
135
|
-
newsReadState: path.join(workspacePath, WORKSPACE_FILES.newsReadState),
|
|
212
|
+
...WORKSPACE_DIR_PATHS,
|
|
213
|
+
...WORKSPACE_FILE_PATHS,
|
|
136
214
|
} as const;
|
|
137
215
|
|
|
138
216
|
export type WorkspaceDirKey = keyof typeof WORKSPACE_DIRS;
|
|
@@ -144,7 +222,6 @@ export type WorkspacePathKey = keyof typeof WORKSPACE_PATHS;
|
|
|
144
222
|
// list is created lazily (first write) by its owning module.
|
|
145
223
|
export const EAGER_WORKSPACE_DIRS: readonly WorkspaceDirKey[] = [
|
|
146
224
|
"chat",
|
|
147
|
-
"todos",
|
|
148
225
|
"calendar",
|
|
149
226
|
"contacts",
|
|
150
227
|
"scheduler",
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { BUILTIN_ROLES, RoleSchema, type Role } from "../../src/config/roles.js";
|
|
3
|
-
import { WORKSPACE_DIRS } from "./paths.js";
|
|
3
|
+
import { WORKSPACE_DIRS, workspacePath } from "./paths.js";
|
|
4
4
|
import { readdirUnderSync, readTextUnderSync } from "../utils/files/workspace-io.js";
|
|
5
|
-
import { workspacePath } from "./paths.js";
|
|
6
|
-
|
|
7
|
-
function withSwitchRole(role: Role): Role {
|
|
8
|
-
if (role.availablePlugins.includes("switchRole")) return role;
|
|
9
|
-
return {
|
|
10
|
-
...role,
|
|
11
|
-
availablePlugins: [...role.availablePlugins, "switchRole"],
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
5
|
|
|
15
6
|
export function loadCustomRoles(): Role[] {
|
|
16
7
|
return readdirUnderSync(workspacePath, WORKSPACE_DIRS.roles)
|
|
@@ -19,7 +10,7 @@ export function loadCustomRoles(): Role[] {
|
|
|
19
10
|
try {
|
|
20
11
|
const raw = readTextUnderSync(workspacePath, path.posix.join(WORKSPACE_DIRS.roles, fileName));
|
|
21
12
|
if (!raw) return [];
|
|
22
|
-
return [
|
|
13
|
+
return [RoleSchema.parse(JSON.parse(raw))];
|
|
23
14
|
} catch {
|
|
24
15
|
return [];
|
|
25
16
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// pulling in a YAML parser we do line-by-line extraction.
|
|
7
7
|
|
|
8
8
|
import { TIME_UNIT_MS, ONE_SECOND_MS } from "../../utils/time.js";
|
|
9
|
+
import { LEADING_BLANK_LINES_PATTERN } from "../../utils/regex.js";
|
|
10
|
+
import { parseFrontmatter } from "../../utils/markdown/frontmatter.js";
|
|
9
11
|
import { SCHEDULE_TYPES } from "@receptron/task-scheduler";
|
|
10
12
|
|
|
11
13
|
export interface SkillSchedule {
|
|
@@ -24,21 +26,6 @@ export interface ParsedSkill {
|
|
|
24
26
|
roleId?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
// Match a YAML scalar value on a single line:
|
|
28
|
-
// description: Enable CI for a repository
|
|
29
|
-
// description: "Quoted with colons: inside"
|
|
30
|
-
// Leading/trailing whitespace trimmed. Quoted values have their
|
|
31
|
-
// outer quotes stripped but inner JSON-style escapes are NOT
|
|
32
|
-
// reversed — SKILL.md descriptions in the wild are plain text.
|
|
33
|
-
function parseScalar(raw: string): string {
|
|
34
|
-
const trimmed = raw.trim();
|
|
35
|
-
if (trimmed.length === 0) return "";
|
|
36
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
37
|
-
return trimmed.slice(1, -1);
|
|
38
|
-
}
|
|
39
|
-
return trimmed;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
29
|
/**
|
|
43
30
|
* Parse schedule value from frontmatter.
|
|
44
31
|
* Supported formats:
|
|
@@ -68,8 +55,8 @@ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
|
|
|
68
55
|
// interval Ns / Nm / Nh — must be >= MIN_INTERVAL_MS
|
|
69
56
|
const intervalMatch = trimmed.match(/^interval\s+(\d+)([smh])$/);
|
|
70
57
|
if (intervalMatch) {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
58
|
+
const [, valueRaw, unit] = intervalMatch;
|
|
59
|
+
const value = Number(valueRaw);
|
|
73
60
|
const unitMs = TIME_UNIT_MS[unit];
|
|
74
61
|
if (!unitMs) return null;
|
|
75
62
|
const intervalMs = value * unitMs;
|
|
@@ -80,6 +67,26 @@ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
|
|
|
80
67
|
return null;
|
|
81
68
|
}
|
|
82
69
|
|
|
70
|
+
/** Type guard — coerce the parsed-meta value at `key` into a
|
|
71
|
+
* string, mirroring the legacy `parseScalar` semantics so this
|
|
72
|
+
* refactor stays behaviour-neutral (codex review iter-1 #908):
|
|
73
|
+
*
|
|
74
|
+
* - key absent → null (legacy bailed out the same way)
|
|
75
|
+
* - string value → as-is, including the empty string
|
|
76
|
+
* - `null` value (from a bare `description:` line that js-yaml
|
|
77
|
+
* returns as `null`) → empty string, matching parseScalar's
|
|
78
|
+
* "" return for an empty raw value
|
|
79
|
+
* - structured / number → null (legacy didn't accept either —
|
|
80
|
+
* parseScalar's input is always a string slice)
|
|
81
|
+
*/
|
|
82
|
+
function metaString(meta: Record<string, unknown>, key: string): string | null {
|
|
83
|
+
if (!(key in meta)) return null;
|
|
84
|
+
const value = meta[key];
|
|
85
|
+
if (typeof value === "string") return value;
|
|
86
|
+
if (value === null) return "";
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
/**
|
|
84
91
|
* Parse a SKILL.md file. Returns null when:
|
|
85
92
|
* - the file has no frontmatter (no leading `---` fence)
|
|
@@ -87,50 +94,26 @@ function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
|
|
|
87
94
|
* - there is no `description:` key
|
|
88
95
|
*
|
|
89
96
|
* An empty body is allowed (the skill may be just metadata for now).
|
|
97
|
+
*
|
|
98
|
+
* Built on the shared `parseFrontmatter` helper (#895 PR C) so the
|
|
99
|
+
* envelope / scalar / quote handling matches the rest of the
|
|
100
|
+
* codebase. Only `description`, `schedule`, and `roleId` are
|
|
101
|
+
* extracted — extra keys in a SKILL.md file are silently ignored.
|
|
90
102
|
*/
|
|
91
|
-
// Extract key-value pairs from YAML frontmatter lines. Returns a
|
|
92
|
-
// map of key → scalar value. Keeps parseSkillFrontmatter under the
|
|
93
|
-
// cognitive-complexity threshold.
|
|
94
|
-
function extractFrontmatterFields(lines: string[], startIdx: number, endIdx: number): Map<string, string> {
|
|
95
|
-
const fields = new Map<string, string>();
|
|
96
|
-
for (let i = startIdx; i < endIdx; i++) {
|
|
97
|
-
const line = lines[i];
|
|
98
|
-
const colonIdx = line.indexOf(":");
|
|
99
|
-
if (colonIdx === -1) continue;
|
|
100
|
-
const key = line.slice(0, colonIdx).trim();
|
|
101
|
-
const value = parseScalar(line.slice(colonIdx + 1));
|
|
102
|
-
fields.set(key, value);
|
|
103
|
-
}
|
|
104
|
-
return fields;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
103
|
export function parseSkillFrontmatter(raw: string): ParsedSkill | null {
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
let closeIdx = -1;
|
|
112
|
-
for (let i = 1; i < lines.length; i++) {
|
|
113
|
-
if (lines[i].trim() === "---") {
|
|
114
|
-
closeIdx = i;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (closeIdx === -1) return null;
|
|
104
|
+
const parsed = parseFrontmatter(raw);
|
|
105
|
+
if (!parsed.hasHeader) return null;
|
|
119
106
|
|
|
120
|
-
const
|
|
121
|
-
const description = fields.get("description") ?? null;
|
|
107
|
+
const description = metaString(parsed.meta, "description");
|
|
122
108
|
if (description === null) return null;
|
|
123
109
|
|
|
124
|
-
const scheduleRaw =
|
|
125
|
-
const roleId =
|
|
110
|
+
const scheduleRaw = metaString(parsed.meta, "schedule");
|
|
111
|
+
const roleId = metaString(parsed.meta, "roleId");
|
|
126
112
|
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.join("\n")
|
|
132
|
-
.replace(/^(?:\s*\n)+/, "")
|
|
133
|
-
.trimEnd();
|
|
113
|
+
// Trim leading blank lines so the UI doesn't render an awkward
|
|
114
|
+
// gap above the first heading. Pattern + ReDoS-safety rationale
|
|
115
|
+
// lives in `server/utils/regex.ts`.
|
|
116
|
+
const body = parsed.body.replace(LEADING_BLANK_LINES_PATTERN, "").trimEnd();
|
|
134
117
|
|
|
135
118
|
const result: ParsedSkill = { description, body };
|
|
136
119
|
if (scheduleRaw) {
|
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
import { loadUserTasks as loadRaw, saveUserTasks } from "../../utils/files/user-tasks-io.js";
|
|
11
11
|
import type { MissedRunPolicy } from "@receptron/task-scheduler";
|
|
12
12
|
import { SCHEDULE_TYPES, MISSED_RUN_POLICIES } from "@receptron/task-scheduler";
|
|
13
|
-
import type { TaskSchedule as LocalTaskSchedule } from "../../events/task-manager/index.js";
|
|
13
|
+
import type { TaskSchedule as LocalTaskSchedule, ITaskManager } from "../../events/task-manager/index.js";
|
|
14
14
|
import { DEFAULT_ROLE_ID } from "../../../src/config/roles.js";
|
|
15
15
|
import { SESSION_ORIGINS, type SessionOrigin } from "../../../src/types/session.js";
|
|
16
16
|
import { log } from "../../system/logger/index.js";
|
|
17
|
-
import type { ITaskManager } from "../../events/task-manager/index.js";
|
|
18
17
|
import { isRecord } from "../../utils/types.js";
|
|
19
18
|
import { makeUuid } from "../../utils/id.js";
|
|
20
19
|
|