mulmoclaude 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// sandbox toggle + server CPU load ratio) plus a one-shot fetch
|
|
5
|
-
// that populates them on mount, plus an optional periodic refresh
|
|
6
|
-
// for the CPU ratio (the favicon's "overloaded" rule needs a live
|
|
7
|
-
// signal, not a boot-time snapshot).
|
|
8
|
-
//
|
|
9
|
-
// On fetch failure we assume Gemini is unavailable so dependent UI
|
|
10
|
-
// (e.g. the "generate image" plugin buttons) falls back gracefully
|
|
11
|
-
// — the sandbox flag keeps its initial `true` so the lock indicator
|
|
12
|
-
// doesn't momentarily flash "sandbox disabled" on a transient error,
|
|
13
|
-
// and the CPU ratio goes to null so the favicon resolver skips the
|
|
14
|
-
// overloaded rule rather than guessing.
|
|
1
|
+
// On fetch failure: assume gemini unavailable (so "generate image" buttons fall back gracefully); keep sandbox=true
|
|
2
|
+
// so the lock indicator doesn't flash off on a transient error; null cpu so the favicon resolver skips overloaded
|
|
3
|
+
// rather than guessing.
|
|
15
4
|
|
|
16
5
|
import { computed, onScopeDispose, ref, type ComputedRef, type Ref } from "vue";
|
|
17
6
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
18
7
|
import { apiGet } from "../utils/api";
|
|
19
8
|
|
|
20
|
-
//
|
|
21
|
-
// favicon. Shorter would mostly flap on short-lived spikes that
|
|
22
|
-
// aren't actually user-visible as lag.
|
|
9
|
+
// 15s catches sustained load without flapping on short-lived spikes that aren't user-visible as lag.
|
|
23
10
|
const HEALTH_REFRESH_MS = 15_000;
|
|
24
11
|
|
|
25
12
|
interface CpuPayload {
|
|
@@ -44,37 +31,26 @@ export function useHealth(): {
|
|
|
44
31
|
const cpuLoad1 = ref<number | null>(null);
|
|
45
32
|
const cpuCores = ref<number | null>(null);
|
|
46
33
|
|
|
47
|
-
//
|
|
48
|
-
// `geminiAvailable` back to false after a successful boot-time
|
|
49
|
-
// fetch. `geminiAvailable` / `sandboxEnabled` are config-derived
|
|
50
|
-
// and don't change at runtime — once we've observed them once,
|
|
51
|
-
// the next 15 s poll's network blip shouldn't mask them.
|
|
34
|
+
// gemini/sandbox are config-derived and don't change at runtime; once observed, a poll blip shouldn't unmask them.
|
|
52
35
|
let bootFetchCompleted = false;
|
|
53
36
|
|
|
54
37
|
async function fetchHealth(): Promise<void> {
|
|
55
38
|
const result = await apiGet<HealthResponse>(API_ROUTES.health);
|
|
56
39
|
if (!result.ok) {
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// closed behaviour. The config flags keep their last-known
|
|
60
|
-
// values, and stay at the initial defaults if we never
|
|
61
|
-
// succeeded (gemini=true → request lands, gets an auth error
|
|
62
|
-
// handled elsewhere; sandbox=true → lock indicator reads on).
|
|
40
|
+
// Null only the CPU figures — the resolver reads null as "skip overloaded" (fail-closed). Config flags keep
|
|
41
|
+
// their last-known values, defaults if never observed (gemini=true → auth error elsewhere, sandbox=true → on).
|
|
63
42
|
cpuLoad1.value = null;
|
|
64
43
|
cpuCores.value = null;
|
|
65
44
|
if (!bootFetchCompleted) {
|
|
66
|
-
//
|
|
67
|
-
// the "Gemini key required" banner can show immediately
|
|
68
|
-
// without waiting for a second attempt. Subsequent poll
|
|
69
|
-
// failures don't re-enter this branch.
|
|
45
|
+
// First-fetch only: flip gemini → false so the "Gemini key required" banner shows without waiting for retry.
|
|
70
46
|
geminiAvailable.value = false;
|
|
71
47
|
}
|
|
72
48
|
return;
|
|
73
49
|
}
|
|
74
|
-
geminiAvailable.value =
|
|
75
|
-
sandboxEnabled.value =
|
|
50
|
+
geminiAvailable.value = Boolean(result.data.geminiAvailable);
|
|
51
|
+
sandboxEnabled.value = Boolean(result.data.sandboxEnabled);
|
|
76
52
|
bootFetchCompleted = true;
|
|
77
|
-
const cpu = result.data
|
|
53
|
+
const { cpu } = result.data;
|
|
78
54
|
if (cpu && typeof cpu.load1 === "number" && Number.isFinite(cpu.load1) && typeof cpu.cores === "number" && cpu.cores > 0) {
|
|
79
55
|
cpuLoad1.value = cpu.load1;
|
|
80
56
|
cpuCores.value = cpu.cores;
|
|
@@ -84,20 +60,15 @@ export function useHealth(): {
|
|
|
84
60
|
}
|
|
85
61
|
}
|
|
86
62
|
|
|
87
|
-
//
|
|
88
|
-
// (gemini / sandbox) don't change at runtime so re-fetching them
|
|
89
|
-
// is waste; but piggy-backing on the same endpoint keeps the
|
|
90
|
-
// server side to a single route and the client to a single poll.
|
|
63
|
+
// Piggy-backs cpu refresh on the health endpoint to keep server + client to a single route/poll.
|
|
91
64
|
const refreshHandle = window.setInterval(() => {
|
|
92
65
|
fetchHealth().catch(() => {
|
|
93
|
-
/*
|
|
94
|
-
favicon's overloaded rule, not user-visible UI */
|
|
66
|
+
/* swallowed: a failed poll just stalls the favicon overloaded rule, not user-visible UI */
|
|
95
67
|
});
|
|
96
68
|
}, HEALTH_REFRESH_MS);
|
|
97
69
|
onScopeDispose(() => window.clearInterval(refreshHandle));
|
|
98
70
|
|
|
99
|
-
//
|
|
100
|
-
// per logical core). Null when either component is missing.
|
|
71
|
+
// load1 per logical core; null if either side is missing.
|
|
101
72
|
const cpuLoadRatio = computed<number | null>(() => {
|
|
102
73
|
if (cpuLoad1.value === null || cpuCores.value === null) return null;
|
|
103
74
|
return cpuLoad1.value / cpuCores.value;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { onMounted, onBeforeUnmount } from "vue";
|
|
2
|
+
import { IMAGE_REPAIR_PATTERN, IMAGE_REPAIR_INLINE_SCRIPT } from "../utils/image/imageRepairInlineScript.js";
|
|
3
|
+
|
|
4
|
+
// Re-exported from the pure module so existing callers keep working.
|
|
5
|
+
// New callers (server/index.ts splice, future iframe-injection
|
|
6
|
+
// surfaces) should import from `../utils/image/imageRepairInlineScript.js`
|
|
7
|
+
// directly to avoid pulling Vue lifecycle hooks into Node code paths.
|
|
8
|
+
export { IMAGE_REPAIR_PATTERN, IMAGE_REPAIR_INLINE_SCRIPT };
|
|
9
|
+
|
|
10
|
+
// Whitespace- and comma-bounded URL token inside a `srcset` value.
|
|
11
|
+
// `srcset` is a comma-list of `<url> [descriptor]` entries; the
|
|
12
|
+
// regex picks each non-whitespace, non-comma run so the descriptor
|
|
13
|
+
// (`1x`, `2x`, `100w`, …) survives the repair pass untouched.
|
|
14
|
+
const SRCSET_TOKEN_RE = /[^\s,]+/g;
|
|
15
|
+
|
|
16
|
+
export function repairImageSrc(img: HTMLImageElement): boolean {
|
|
17
|
+
if (img.dataset.imageRepairTried) return false;
|
|
18
|
+
// Set the one-shot marker only AFTER confirming the URL matches the
|
|
19
|
+
// repair pattern. Otherwise an unrelated 404 (different domain, no
|
|
20
|
+
// artifacts/images segment) would pin the marker and silently block
|
|
21
|
+
// any future repair attempt on the same DOM element if the src is
|
|
22
|
+
// later replaced with a repairable one.
|
|
23
|
+
const match = img.src.match(IMAGE_REPAIR_PATTERN);
|
|
24
|
+
if (!match) return false;
|
|
25
|
+
img.dataset.imageRepairTried = "1";
|
|
26
|
+
img.src = `/${match[0]}`;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Repair a `<source>` element used inside `<picture>` / `<audio>` /
|
|
31
|
+
// `<video>`. Handles both shapes:
|
|
32
|
+
// - `srcset="..."` (the picture form, often comma-list with size
|
|
33
|
+
// descriptors)
|
|
34
|
+
// - `src="..."` (the audio/video form, single URL)
|
|
35
|
+
// One-shot via the same `imageRepairTried` marker as <img>.
|
|
36
|
+
export function repairSourceSrc(source: HTMLSourceElement): boolean {
|
|
37
|
+
if (source.dataset.imageRepairTried) return false;
|
|
38
|
+
let repaired = false;
|
|
39
|
+
const src = source.getAttribute("src");
|
|
40
|
+
if (src) {
|
|
41
|
+
const match = src.match(IMAGE_REPAIR_PATTERN);
|
|
42
|
+
if (match) {
|
|
43
|
+
source.setAttribute("src", `/${match[0]}`);
|
|
44
|
+
repaired = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (source.srcset) {
|
|
48
|
+
const original = source.srcset;
|
|
49
|
+
const next = original.replace(SRCSET_TOKEN_RE, (token) => {
|
|
50
|
+
const tokenMatch = token.match(IMAGE_REPAIR_PATTERN);
|
|
51
|
+
return tokenMatch ? `/${tokenMatch[0]}` : token;
|
|
52
|
+
});
|
|
53
|
+
if (next !== original) {
|
|
54
|
+
source.srcset = next;
|
|
55
|
+
repaired = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (repaired) source.dataset.imageRepairTried = "1";
|
|
59
|
+
return repaired;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Attach a document-level capture-phase error listener so any
|
|
63
|
+
// `<img>` / `<source>` / `<audio>` / `<video>` 404 in the app
|
|
64
|
+
// shell (wiki / markdown / news / Files preview etc) gets one
|
|
65
|
+
// repair attempt. Capture phase is required because the relevant
|
|
66
|
+
// error events don't bubble. The repair is a no-op for src values
|
|
67
|
+
// that don't match the artifacts/images pattern, so attaching at
|
|
68
|
+
// document scope is safe — it never touches non-image-bearing UI.
|
|
69
|
+
export function useGlobalImageErrorRepair(): void {
|
|
70
|
+
function onError(event: Event): void {
|
|
71
|
+
const { target } = event;
|
|
72
|
+
if (target instanceof HTMLImageElement) {
|
|
73
|
+
repairImageSrc(target);
|
|
74
|
+
// Source-element error events don't fire reliably in Chromium
|
|
75
|
+
// when a `<picture><source>` srcset 404s — only the inner
|
|
76
|
+
// `<img>` reaches a target. Walk siblings so a wrong-prefix
|
|
77
|
+
// `<source>` next to a repairable `<img>` gets the same fix.
|
|
78
|
+
const picture = target.closest("picture");
|
|
79
|
+
if (picture) {
|
|
80
|
+
for (const src of picture.querySelectorAll("source")) {
|
|
81
|
+
repairSourceSrc(src);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} else if (target instanceof HTMLSourceElement) {
|
|
85
|
+
repairSourceSrc(target);
|
|
86
|
+
} else if (target instanceof HTMLMediaElement) {
|
|
87
|
+
// `<audio>` / `<video>` fire `error` on themselves when ALL
|
|
88
|
+
// their `<source>` children fail. The source elements never
|
|
89
|
+
// get a target of their own in that path, so reach into
|
|
90
|
+
// each child and repair it.
|
|
91
|
+
for (const src of target.querySelectorAll<HTMLSourceElement>(":scope > source")) {
|
|
92
|
+
repairSourceSrc(src);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onMounted(() => {
|
|
98
|
+
document.addEventListener("error", onError, { capture: true });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
onBeforeUnmount(() => {
|
|
102
|
+
document.removeEventListener("error", onError, { capture: true });
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// #876. Distinguish data===null ("no journal yet") from response.ok===false ("load failed") so a real auth/network/
|
|
2
|
+
// backend failure isn't misreported as empty state (Codex review iter 1). Crude alerts pending a toast composable.
|
|
3
|
+
|
|
4
|
+
import { ref } from "vue";
|
|
5
|
+
import { useRouter } from "vue-router";
|
|
6
|
+
import { useI18n } from "vue-i18n";
|
|
7
|
+
import { apiGet } from "../utils/api";
|
|
8
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
9
|
+
|
|
10
|
+
interface LatestDailyResult {
|
|
11
|
+
path: string;
|
|
12
|
+
isoDate: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useLatestDaily() {
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const { t } = useI18n();
|
|
18
|
+
const loading = ref(false);
|
|
19
|
+
|
|
20
|
+
async function openLatestDaily(): Promise<void> {
|
|
21
|
+
if (loading.value) return;
|
|
22
|
+
loading.value = true;
|
|
23
|
+
try {
|
|
24
|
+
const response = await apiGet<LatestDailyResult | null>(API_ROUTES.journal.latestDaily);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
window.alert(t("sidebarHeader.todayJournalLoadFailed", { status: response.status, error: response.error }));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (response.data === null) {
|
|
30
|
+
window.alert(t("sidebarHeader.todayJournalNotFound"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await router.push(`/files/${response.data.path}`);
|
|
34
|
+
} finally {
|
|
35
|
+
loading.value = false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { openLatestDaily, loading };
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// #895 PR A: shared frontmatter handling for every markdown-from-disk view. parseFrontmatter never throws —
|
|
2
|
+
// malformed header degrades to "render the whole input as body" instead of breaking the view.
|
|
3
|
+
|
|
4
|
+
import { computed, type ComputedRef, type Ref } from "vue";
|
|
5
|
+
import { parseFrontmatter, type ParsedMarkdown } from "../utils/markdown/frontmatter";
|
|
6
|
+
|
|
7
|
+
export interface MarkdownDocField {
|
|
8
|
+
key: string;
|
|
9
|
+
// Templates branch on Array.isArray and pass scalars through formatScalarField — nested objects would otherwise
|
|
10
|
+
// render as `[object Object]` (codex review iter-1 #902).
|
|
11
|
+
value: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function formatScalarField(value: unknown): string {
|
|
15
|
+
if (value === null || value === undefined) return "";
|
|
16
|
+
if (typeof value === "object") {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
} catch {
|
|
20
|
+
// Cyclic object → can't stringify; fall back to String() rather than throw inside a template.
|
|
21
|
+
return String(value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MarkdownDocView extends ParsedMarkdown {
|
|
28
|
+
fields: MarkdownDocField[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Pass null/undefined to get the empty state — so callers can wire a load-state ref without a null-guard wrapper.
|
|
32
|
+
export function useMarkdownDoc(content: Ref<string | null | undefined>): ComputedRef<MarkdownDocView> {
|
|
33
|
+
return computed(() => {
|
|
34
|
+
const raw = content.value ?? "";
|
|
35
|
+
const parsed = parseFrontmatter(raw);
|
|
36
|
+
const fields = Object.entries(parsed.meta).map(([key, value]) => ({ key, value }));
|
|
37
|
+
return { ...parsed, fields };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -16,7 +16,7 @@ export function useMarkdownLinkHandler(selectedPath: Ref<string | null>, handler
|
|
|
16
16
|
function handleMarkdownLinkClick(event: MouseEvent): void {
|
|
17
17
|
if (event.button !== 0) return;
|
|
18
18
|
if (event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
19
|
-
const target = event
|
|
19
|
+
const { target } = event;
|
|
20
20
|
if (!(target instanceof Element)) return;
|
|
21
21
|
const anchor = target.closest("a");
|
|
22
22
|
if (!anchor) return;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// Composable that owns the MCP tool state used by the sidebar:
|
|
2
|
-
// which tools are currently disabled, their per-tool prompts, and
|
|
3
|
-
// the derived `availableTools` / `toolDescriptions` computeds. The
|
|
4
|
-
// pure rules live in src/utils/mcpTools so they are unit-testable
|
|
5
|
-
// independently of fetch / Vue.
|
|
6
|
-
|
|
7
1
|
import { computed, ref, type ComputedRef } from "vue";
|
|
8
2
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
9
3
|
import type { Role } from "../config/roles";
|
|
@@ -12,20 +6,14 @@ import { apiGet } from "../utils/api";
|
|
|
12
6
|
|
|
13
7
|
interface UseMcpToolsOptions {
|
|
14
8
|
currentRole: ComputedRef<Role>;
|
|
15
|
-
//
|
|
16
|
-
// callers pass `(name) => getPlugin(name)?.toolDefinition ?? null`,
|
|
17
|
-
// tests can stub it.
|
|
9
|
+
// Plugin-registry lookup, injectable so tests can stub it.
|
|
18
10
|
getDefinition: (name: string) => ToolDefinitionMetadata | null;
|
|
19
11
|
}
|
|
20
12
|
|
|
21
13
|
export function useMcpTools(opts: UseMcpToolsOptions) {
|
|
22
14
|
const disabledMcpTools = ref(new Set<string>());
|
|
23
15
|
const mcpToolDescriptions = ref<Record<string, string>>({});
|
|
24
|
-
// Surfaces
|
|
25
|
-
// (e.g. the Settings modal's MCP tab) can render a small warning.
|
|
26
|
-
// We intentionally keep the "all tools visible" fallback below so
|
|
27
|
-
// the UI stays usable; this ref lets the UI tell the user *why* the
|
|
28
|
-
// list looks incomplete / unfiltered.
|
|
16
|
+
// Surfaces /api/mcp-tools failures so the Settings MCP tab can explain *why* the list looks unfiltered.
|
|
29
17
|
const mcpToolsError = ref<string | null>(null);
|
|
30
18
|
|
|
31
19
|
const availableTools = computed(() => availableToolsFor(opts.currentRole.value.availablePlugins, disabledMcpTools.value));
|
|
@@ -46,8 +34,7 @@ export function useMcpTools(opts: UseMcpToolsOptions) {
|
|
|
46
34
|
const result = await apiGet<McpToolStatus[]>(API_ROUTES.mcpTools.list);
|
|
47
35
|
if (!result.ok) {
|
|
48
36
|
mcpToolsError.value = result.error;
|
|
49
|
-
//
|
|
50
|
-
// disabledMcpTools or descriptions means the UI remains usable.
|
|
37
|
+
// Don't clear disabledMcpTools / descriptions — falling back to "all tools visible" keeps the UI usable.
|
|
51
38
|
return;
|
|
52
39
|
}
|
|
53
40
|
if (!Array.isArray(result.data)) {
|
|
@@ -1,95 +1,104 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Stores incoming NotificationPayloads for the bell badge + panel.
|
|
1
|
+
// Bell-side composable backed by the notifier engine.
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// PR 4 of feat-encore migrated `publishNotification()` onto the new
|
|
4
|
+
// engine, so the bell consumes the same data as the dev-mode debug
|
|
5
|
+
// popup: a single global pubsub channel (`PUBSUB_CHANNELS.notifier`),
|
|
6
|
+
// primed via `POST /api/notifier {action: "list" | "listHistory"}`.
|
|
8
7
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
8
|
+
// Singleton state shared across consumers: a single subscription is
|
|
9
|
+
// shared between the bell badge, the panel, and any other surface
|
|
10
|
+
// that wants to render the same source of truth. Subscriber counting
|
|
11
|
+
// + ref-counted teardown matches the legacy composable's pattern so
|
|
12
|
+
// the websocket subscription doesn't leak when every consumer
|
|
13
|
+
// unmounts.
|
|
14
|
+
|
|
15
|
+
import { computed, onUnmounted, ref, type ComputedRef, type Ref } from "vue";
|
|
16
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
18
17
|
import { PUBSUB_CHANNELS } from "../config/pubsubChannels";
|
|
18
|
+
import { apiPost } from "../utils/api";
|
|
19
19
|
import { usePubSub } from "./usePubSub";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!isValidAction(value.action)) return false;
|
|
36
|
-
return true;
|
|
20
|
+
|
|
21
|
+
// Mirror of `server/notifier/types.ts` — repeated here rather than
|
|
22
|
+
// imported because the server tree pulls in fs / pubsub deps that
|
|
23
|
+
// shouldn't bleed into the client bundle. The two definitions are
|
|
24
|
+
// kept in sync by code review.
|
|
25
|
+
export interface NotifierEntry {
|
|
26
|
+
id: string;
|
|
27
|
+
pluginPkg: string;
|
|
28
|
+
severity: "info" | "nudge" | "urgent";
|
|
29
|
+
lifecycle?: "fyi" | "action";
|
|
30
|
+
title: string;
|
|
31
|
+
body?: string;
|
|
32
|
+
navigateTarget?: string;
|
|
33
|
+
pluginData?: unknown;
|
|
34
|
+
createdAt: string;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// landing in the panel and crashing later in the click handler.
|
|
43
|
-
function isValidAction(action: unknown): boolean {
|
|
44
|
-
if (!isRecord(action)) return false;
|
|
45
|
-
if (action.type === NOTIFICATION_ACTION_TYPES.none) return true;
|
|
46
|
-
if (action.type !== NOTIFICATION_ACTION_TYPES.navigate) return false;
|
|
47
|
-
const target = action.target;
|
|
48
|
-
if (!isRecord(target)) return false;
|
|
49
|
-
return typeof target.view === "string" && VALID_VIEWS.has(target.view);
|
|
37
|
+
export interface NotifierHistoryEntry extends NotifierEntry {
|
|
38
|
+
terminalType: "cleared" | "cancelled";
|
|
39
|
+
terminalAt: string;
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
42
|
+
type NotifierEvent = { type: "published"; entry: NotifierEntry } | { type: "cleared"; id: string } | { type: "cancelled"; id: string };
|
|
43
|
+
|
|
44
|
+
const HISTORY_CAP = 50;
|
|
45
|
+
|
|
46
|
+
const entries = ref<NotifierEntry[]>([]);
|
|
47
|
+
const history = ref<NotifierHistoryEntry[]>([]);
|
|
48
|
+
const ready = ref(false);
|
|
58
49
|
|
|
59
|
-
// Singleton subscription — ref-counted across consumers.
|
|
60
50
|
let subscriberCount = 0;
|
|
61
51
|
let unsubscribeFn: (() => void) | null = null;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
52
|
+
let primePromise: Promise<void> | null = null;
|
|
53
|
+
|
|
54
|
+
async function prime(): Promise<void> {
|
|
55
|
+
if (primePromise) return primePromise;
|
|
56
|
+
primePromise = (async () => {
|
|
57
|
+
const [activeResult, historyResult] = await Promise.all([
|
|
58
|
+
apiPost<{ entries: NotifierEntry[] }>(API_ROUTES.notifier.dispatch, { action: "list" }),
|
|
59
|
+
apiPost<{ history: NotifierHistoryEntry[] }>(API_ROUTES.notifier.dispatch, { action: "listHistory" }),
|
|
60
|
+
]);
|
|
61
|
+
if (activeResult.ok) entries.value = activeResult.data.entries;
|
|
62
|
+
if (historyResult.ok) history.value = historyResult.data.history;
|
|
63
|
+
ready.value = true;
|
|
64
|
+
})();
|
|
65
|
+
return primePromise;
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
68
|
+
function applyEvent(event: NotifierEvent): void {
|
|
69
|
+
switch (event.type) {
|
|
70
|
+
case "published":
|
|
71
|
+
// Dedup against optimistic local update — the host UI clear
|
|
72
|
+
// button already removed the entry; ignore the echoing event.
|
|
73
|
+
if (!entries.value.some((entry) => entry.id === event.entry.id)) {
|
|
74
|
+
entries.value = [...entries.value, event.entry];
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
case "cleared":
|
|
78
|
+
case "cancelled": {
|
|
79
|
+
const removed = entries.value.find((entry) => entry.id === event.id);
|
|
80
|
+
entries.value = entries.value.filter((entry) => entry.id !== event.id);
|
|
81
|
+
if (removed) {
|
|
82
|
+
const historyEntry: NotifierHistoryEntry = {
|
|
83
|
+
...removed,
|
|
84
|
+
terminalType: event.type === "cleared" ? "cleared" : "cancelled",
|
|
85
|
+
terminalAt: new Date().toISOString(),
|
|
86
|
+
};
|
|
87
|
+
history.value = [historyEntry, ...history.value].slice(0, HISTORY_CAP);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
function ensureSubscribed(subscribe: ReturnType<typeof usePubSub>["subscribe"]): void {
|
|
94
|
+
subscriberCount += 1;
|
|
95
|
+
if (unsubscribeFn) return;
|
|
96
|
+
unsubscribeFn = subscribe(PUBSUB_CHANNELS.notifier, (data) => applyEvent(data as NotifierEvent));
|
|
97
|
+
void prime();
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
function releaseSubscription(): void {
|
|
92
|
-
subscriberCount
|
|
101
|
+
subscriberCount -= 1;
|
|
93
102
|
if (subscriberCount <= 0 && unsubscribeFn) {
|
|
94
103
|
unsubscribeFn();
|
|
95
104
|
unsubscribeFn = null;
|
|
@@ -98,56 +107,73 @@ function releaseSubscription(): void {
|
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
export function useNotifications(): {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
entries: ComputedRef<NotifierEntry[]>;
|
|
111
|
+
history: Ref<NotifierHistoryEntry[]>;
|
|
112
|
+
badgeCount: ComputedRef<number>;
|
|
113
|
+
/** Worst-severity-wins Tailwind class. Mirrors the dev debug popup
|
|
114
|
+
* and matches the bell-badge encoding in `feat-notifier-ux.md`. */
|
|
115
|
+
badgeColor: ComputedRef<string>;
|
|
116
|
+
ready: Ref<boolean>;
|
|
117
|
+
clear: (id: string) => Promise<void>;
|
|
118
|
+
cancel: (id: string) => Promise<void>;
|
|
108
119
|
} {
|
|
109
120
|
const { subscribe } = usePubSub();
|
|
110
121
|
ensureSubscribed(subscribe);
|
|
111
122
|
onUnmounted(releaseSubscription);
|
|
112
123
|
|
|
113
|
-
|
|
124
|
+
// Sort oldest-first so a row's vertical position doesn't jump when
|
|
125
|
+
// a fresh entry arrives at the bottom — the same scan order the
|
|
126
|
+
// debug popup uses. The bell panel then visually inverts via flex
|
|
127
|
+
// direction, matching today's "newest at the top" expectation.
|
|
128
|
+
const sortedEntries = computed(() => [...entries.value].sort((left, right) => left.createdAt.localeCompare(right.createdAt)));
|
|
114
129
|
|
|
115
|
-
const
|
|
130
|
+
const badgeCount = computed(() => entries.value.length);
|
|
116
131
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
const badgeColor = computed(() => {
|
|
133
|
+
if (entries.value.some((entry) => entry.severity === "urgent")) return "bg-red-500";
|
|
134
|
+
if (entries.value.some((entry) => entry.severity === "nudge")) return "bg-amber-500";
|
|
135
|
+
return "bg-gray-400";
|
|
136
|
+
});
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
/** Remove an entry from the active list and prepend a synthetic
|
|
139
|
+
* history record. Used optimistically by `clear` / `cancel` so the
|
|
140
|
+
* bell reacts before the server round-trip completes; `applyEvent`
|
|
141
|
+
* is idempotent on the eventual pubsub echo (the entry is already
|
|
142
|
+
* gone from `entries`, so its `cleared`/`cancelled` branch finds
|
|
143
|
+
* nothing to remove and skips the history append).
|
|
144
|
+
*
|
|
145
|
+
* No-op if the entry was already taken out (e.g. another tab raced
|
|
146
|
+
* us via the pubsub event); preserves single-history-entry-per-id
|
|
147
|
+
* semantics. */
|
|
148
|
+
function moveToHistoryLocally(entryId: string, terminalType: "cleared" | "cancelled"): void {
|
|
149
|
+
const removed = entries.value.find((entry) => entry.id === entryId);
|
|
150
|
+
if (!removed) return;
|
|
151
|
+
entries.value = entries.value.filter((entry) => entry.id !== entryId);
|
|
152
|
+
const historyEntry: NotifierHistoryEntry = {
|
|
153
|
+
...removed,
|
|
154
|
+
terminalType,
|
|
155
|
+
terminalAt: new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
history.value = [historyEntry, ...history.value].slice(0, HISTORY_CAP);
|
|
128
158
|
}
|
|
129
159
|
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
160
|
+
async function clear(entryId: string): Promise<void> {
|
|
161
|
+
moveToHistoryLocally(entryId, "cleared");
|
|
162
|
+
const result = await apiPost<{ ok: true }>(API_ROUTES.notifier.dispatch, { action: "clear", id: entryId });
|
|
163
|
+
if (!result.ok) {
|
|
164
|
+
// Rendering this in console rather than the panel itself —
|
|
165
|
+
// the panel is small and we don't want a transient API error
|
|
166
|
+
// shouting at the user. A future prime() would resync state
|
|
167
|
+
// if the server is genuinely out of sync.
|
|
168
|
+
console.error("[useNotifications] clear failed", result.error);
|
|
135
169
|
}
|
|
136
|
-
readIds.value = next;
|
|
137
170
|
}
|
|
138
171
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// ~36-char id per dismissal even though the user can't see
|
|
144
|
-
// them — pruneReadIds keeps the Set tied to `notifications`.
|
|
145
|
-
if (readIds.value.has(notifId)) {
|
|
146
|
-
const next = new Set(readIds.value);
|
|
147
|
-
next.delete(notifId);
|
|
148
|
-
readIds.value = next;
|
|
149
|
-
}
|
|
172
|
+
async function cancel(entryId: string): Promise<void> {
|
|
173
|
+
moveToHistoryLocally(entryId, "cancelled");
|
|
174
|
+
const result = await apiPost<{ ok: true }>(API_ROUTES.notifier.dispatch, { action: "cancel", id: entryId });
|
|
175
|
+
if (!result.ok) console.error("[useNotifications] cancel failed", result.error);
|
|
150
176
|
}
|
|
151
177
|
|
|
152
|
-
return {
|
|
178
|
+
return { entries: sortedEntries, history, badgeCount, badgeColor, ready, clear, cancel };
|
|
153
179
|
}
|