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
|
@@ -26,10 +26,32 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
<div class="markdown-content-wrapper">
|
|
28
28
|
<div class="p-4">
|
|
29
|
+
<!-- Frontmatter properties panel (FileContentRenderer-style)
|
|
30
|
+
— only rendered when the file has a `---\n...\n---`
|
|
31
|
+
header. Lazy-on-write means most existing files don't
|
|
32
|
+
have one yet (#895). -->
|
|
33
|
+
<div v-if="mdDoc.fields.length > 0" class="mb-3 rounded border border-gray-200 bg-gray-50 p-3 text-xs">
|
|
34
|
+
<div v-for="field in mdDoc.fields" :key="field.key" class="flex items-baseline gap-2 py-0.5">
|
|
35
|
+
<span class="font-semibold text-gray-600 shrink-0">{{ field.key }}:</span>
|
|
36
|
+
<template v-if="Array.isArray(field.value)">
|
|
37
|
+
<span class="flex flex-wrap gap-1">
|
|
38
|
+
<span
|
|
39
|
+
v-for="(item, idx) in field.value"
|
|
40
|
+
:key="String(idx) + ':' + formatScalarField(item)"
|
|
41
|
+
class="rounded-full bg-white border border-gray-300 px-2 py-0.5 text-gray-700"
|
|
42
|
+
>
|
|
43
|
+
{{ formatScalarField(item) }}
|
|
44
|
+
</span>
|
|
45
|
+
</span>
|
|
46
|
+
</template>
|
|
47
|
+
<span v-else class="text-gray-800 break-words">{{ formatScalarField(field.value) }}</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
29
50
|
<!-- Click delegation: a single listener on the wrapper picks
|
|
30
51
|
up every interactive checkbox inserted by v-html. We
|
|
31
52
|
cannot bind @click directly on each `<input>` because
|
|
32
53
|
v-html bypasses Vue's template compiler. -->
|
|
54
|
+
<!-- eslint-disable-next-line vue/no-v-html -- marked.parse output of app-owned markdown content; trusted in-process render -->
|
|
33
55
|
<div class="markdown-content prose prose-slate max-w-none" @click="onMarkdownClick" v-html="renderedHtml"></div>
|
|
34
56
|
</div>
|
|
35
57
|
</div>
|
|
@@ -58,18 +80,20 @@
|
|
|
58
80
|
import { computed, ref, watch, nextTick } from "vue";
|
|
59
81
|
import { useI18n } from "vue-i18n";
|
|
60
82
|
import { marked } from "marked";
|
|
61
|
-
|
|
62
|
-
const { t } = useI18n();
|
|
83
|
+
import { formatScalarField, useMarkdownDoc } from "../../composables/useMarkdownDoc";
|
|
63
84
|
import type { ToolResult } from "gui-chat-protocol";
|
|
64
|
-
import { isFilePath, type MarkdownToolData } from "./definition";
|
|
85
|
+
import { isFilePath, type MarkdownToolData, type DocumentEndpoints } from "./definition";
|
|
65
86
|
import { rewriteMarkdownImageRefs } from "../../utils/image/rewriteMarkdownImageRefs";
|
|
66
87
|
import { findTaskLines, makeTasksInteractive, toggleTaskAt } from "../../utils/markdown/taskList";
|
|
67
88
|
import { usePdfDownload } from "../../composables/usePdfDownload";
|
|
68
89
|
import { apiGet, apiPut } from "../../utils/api";
|
|
69
|
-
import {
|
|
90
|
+
import { pluginEndpoints } from "../api";
|
|
70
91
|
import { useClipboardCopy } from "../../composables/useClipboardCopy";
|
|
71
92
|
import { buildPdfFilename } from "../../utils/files/filename";
|
|
72
93
|
import { useAppApi } from "../../composables/useAppApi";
|
|
94
|
+
import { useFileChange } from "../../composables/useFileChange";
|
|
95
|
+
|
|
96
|
+
const { t } = useI18n();
|
|
73
97
|
|
|
74
98
|
const props = defineProps<{
|
|
75
99
|
selectedResult: ToolResult<MarkdownToolData>;
|
|
@@ -94,6 +118,9 @@ const loadError = ref<string | null>(null);
|
|
|
94
118
|
const markdownContent = ref("");
|
|
95
119
|
const editableMarkdown = ref("");
|
|
96
120
|
|
|
121
|
+
const endpoints = pluginEndpoints<DocumentEndpoints>("markdown");
|
|
122
|
+
const filesEndpoints = pluginEndpoints<{ content: string }>("files");
|
|
123
|
+
|
|
97
124
|
async function fetchMarkdownContent(): Promise<void> {
|
|
98
125
|
loadError.value = null;
|
|
99
126
|
const raw = props.selectedResult.data?.markdown;
|
|
@@ -104,7 +131,7 @@ async function fetchMarkdownContent(): Promise<void> {
|
|
|
104
131
|
}
|
|
105
132
|
if (isFilePath(raw)) {
|
|
106
133
|
loading.value = true;
|
|
107
|
-
const result = await apiGet<{ content?: string }>(
|
|
134
|
+
const result = await apiGet<{ content?: string }>(filesEndpoints.content, {
|
|
108
135
|
path: raw,
|
|
109
136
|
});
|
|
110
137
|
if (!result.ok) {
|
|
@@ -128,9 +155,43 @@ async function fetchMarkdownContent(): Promise<void> {
|
|
|
128
155
|
// Fetch on mount
|
|
129
156
|
fetchMarkdownContent();
|
|
130
157
|
|
|
131
|
-
const hasChanges = computed(() =>
|
|
132
|
-
|
|
158
|
+
const hasChanges = computed(() => editableMarkdown.value !== markdownContent.value);
|
|
159
|
+
|
|
160
|
+
// Subscribe to per-file change events so any tab / browser / agent run
|
|
161
|
+
// that overwrites the file refreshes this view automatically. The path
|
|
162
|
+
// passed in is the workspace-relative `data.markdown` (only valid when
|
|
163
|
+
// `isFilePath` — inline legacy content has no on-disk twin).
|
|
164
|
+
const watchedPath = computed(() => {
|
|
165
|
+
const raw = props.selectedResult.data?.markdown;
|
|
166
|
+
return typeof raw === "string" && isFilePath(raw) ? raw : null;
|
|
133
167
|
});
|
|
168
|
+
const { version: fileVersion } = useFileChange(watchedPath);
|
|
169
|
+
|
|
170
|
+
// Declared early so the `fileVersion` watcher below can reach into the
|
|
171
|
+
// `<details>` element to close the editor when a remote write lands.
|
|
172
|
+
const sourceDetails = ref<HTMLDetailsElement>();
|
|
173
|
+
|
|
174
|
+
// Remote write: refetch so the rendered view tracks disk. If the
|
|
175
|
+
// editor is open we close it first — `fileVersion` only fires once
|
|
176
|
+
// per remote write, so leaving the panel open and skipping the fetch
|
|
177
|
+
// would strand the view on stale content until the next write
|
|
178
|
+
// (#1001 P1). Discarding in-progress edits is rare enough to be
|
|
179
|
+
// acceptable; a "remote changed" banner is queued for a follow-up —
|
|
180
|
+
// see plans/done/feat-file-change-pubsub.md.
|
|
181
|
+
watch(fileVersion, (current, previous) => {
|
|
182
|
+
if (current === 0 || current === previous) return;
|
|
183
|
+
if (sourceDetails.value?.open) {
|
|
184
|
+
sourceDetails.value.open = false;
|
|
185
|
+
}
|
|
186
|
+
void fetchMarkdownContent();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Frontmatter-aware view of the loaded content — separates the
|
|
190
|
+
// `---\n...\n---` header (rendered as a properties panel) from the
|
|
191
|
+
// markdown body (passed to marked). Without this split the header
|
|
192
|
+
// would render as a stray `<hr>` plus key:value plain text in
|
|
193
|
+
// every file the LLM saved with frontmatter (#895 PR A).
|
|
194
|
+
const mdDoc = useMarkdownDoc(markdownContent);
|
|
134
195
|
|
|
135
196
|
const renderedHtml = computed(() => {
|
|
136
197
|
if (!markdownContent.value) return "";
|
|
@@ -142,7 +203,7 @@ const renderedHtml = computed(() => {
|
|
|
142
203
|
// references get rewritten.
|
|
143
204
|
const raw = props.selectedResult.data?.markdown;
|
|
144
205
|
const basePath = typeof raw === "string" && isFilePath(raw) ? raw.slice(0, raw.lastIndexOf("/")) : "";
|
|
145
|
-
const withImages = rewriteMarkdownImageRefs(
|
|
206
|
+
const withImages = rewriteMarkdownImageRefs(mdDoc.value.body, basePath);
|
|
146
207
|
// Strip the `disabled=""` attribute marked puts on GFM task
|
|
147
208
|
// checkboxes and tag them so `onMarkdownClick` can find them
|
|
148
209
|
// (#775). Inline content (no file backing) gets the same
|
|
@@ -167,12 +228,11 @@ watch(
|
|
|
167
228
|
},
|
|
168
229
|
);
|
|
169
230
|
|
|
170
|
-
const sourceDetails = ref<HTMLDetailsElement>();
|
|
171
231
|
const editing = ref(false);
|
|
172
232
|
const { copied, copy } = useClipboardCopy();
|
|
173
233
|
|
|
174
234
|
function onDetailsToggle(event: Event) {
|
|
175
|
-
const open =
|
|
235
|
+
const { open } = event.target as HTMLDetailsElement;
|
|
176
236
|
editing.value = open;
|
|
177
237
|
if (!open) {
|
|
178
238
|
editableMarkdown.value = markdownContent.value;
|
|
@@ -194,7 +254,7 @@ async function downloadPdf() {
|
|
|
194
254
|
if (!markdownContent.value) return;
|
|
195
255
|
const prefix = props.selectedResult.data?.filenamePrefix;
|
|
196
256
|
const rawName = prefix || props.selectedResult.title || "";
|
|
197
|
-
const uuid = props.selectedResult
|
|
257
|
+
const { uuid } = props.selectedResult;
|
|
198
258
|
const filename = buildPdfFilename({
|
|
199
259
|
name: rawName,
|
|
200
260
|
fallback: "document",
|
|
@@ -215,7 +275,7 @@ async function applyMarkdown() {
|
|
|
215
275
|
// (e.g. the YYYY/MM partitions added in #764).
|
|
216
276
|
if (isFilePath(raw)) {
|
|
217
277
|
saving.value = true;
|
|
218
|
-
const result = await apiPut<unknown>(
|
|
278
|
+
const result = await apiPut<unknown>(endpoints.update.url, {
|
|
219
279
|
relativePath: raw,
|
|
220
280
|
markdown: editableMarkdown.value,
|
|
221
281
|
});
|
|
@@ -267,7 +327,7 @@ async function persistTaskMarkdown(relativePath: string, markdown: string): Prom
|
|
|
267
327
|
// on screen, and persisting it would clobber unrelated state.
|
|
268
328
|
if (props.selectedResult.data?.markdown !== relativePath) return;
|
|
269
329
|
|
|
270
|
-
const result = await apiPut<unknown>(
|
|
330
|
+
const result = await apiPut<unknown>(endpoints.update.url, {
|
|
271
331
|
relativePath,
|
|
272
332
|
markdown,
|
|
273
333
|
});
|
|
@@ -294,7 +354,7 @@ async function persistTaskMarkdown(relativePath: string, markdown: string): Prom
|
|
|
294
354
|
}
|
|
295
355
|
|
|
296
356
|
function onMarkdownClick(event: MouseEvent): void {
|
|
297
|
-
const target = event
|
|
357
|
+
const { target } = event;
|
|
298
358
|
if (!(target instanceof HTMLInputElement)) return;
|
|
299
359
|
if (target.type !== "checkbox") return;
|
|
300
360
|
if (!target.classList.contains("md-task")) return;
|
|
@@ -317,20 +377,27 @@ function onMarkdownClick(event: MouseEvent): void {
|
|
|
317
377
|
// it as a task; marked treats it as code) — toggling source by
|
|
318
378
|
// index would corrupt the file. Refuse all clicks when this
|
|
319
379
|
// happens.
|
|
320
|
-
|
|
380
|
+
// Walk only the body (the same source `marked` rendered) so
|
|
381
|
+
// frontmatter contents containing `- [ ]`-shaped YAML never
|
|
382
|
+
// collide with task counting (#895 PR A). The prefix is
|
|
383
|
+
// preserved byte-for-byte and re-attached after the toggle.
|
|
384
|
+
const { body } = mdDoc.value;
|
|
385
|
+
const prefix = markdownContent.value.slice(0, markdownContent.value.length - body.length);
|
|
386
|
+
const sourceTasks = findTaskLines(body);
|
|
321
387
|
if (sourceTasks.length !== taskInputs.length) {
|
|
322
388
|
target.checked = !target.checked;
|
|
323
389
|
saveError.value = t("pluginMarkdown.taskCountMismatch");
|
|
324
390
|
return;
|
|
325
391
|
}
|
|
326
392
|
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
393
|
+
const updatedBody = toggleTaskAt(body, taskIndex);
|
|
394
|
+
if (updatedBody === null) {
|
|
329
395
|
// Source/DOM drift — refuse to write something we can't trace.
|
|
330
396
|
target.checked = !target.checked;
|
|
331
397
|
return;
|
|
332
398
|
}
|
|
333
399
|
|
|
400
|
+
const updated = prefix + updatedBody;
|
|
334
401
|
// Optimistic local update — v-html will re-render and the
|
|
335
402
|
// textarea (if anyone opens it next) sees the same content.
|
|
336
403
|
markdownContent.value = updated;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ToolDefinition } from "gui-chat-protocol";
|
|
2
|
+
import { META } from "./meta";
|
|
3
|
+
import type { ResolvedRoute } from "../meta-types";
|
|
2
4
|
|
|
3
|
-
export const TOOL_NAME =
|
|
5
|
+
export const TOOL_NAME = META.toolName;
|
|
6
|
+
/** Resolved-URL view of the markdown plugin's routes — `{ method,
|
|
7
|
+
* url }` per route key, with the `/api/markdown` prefix already
|
|
8
|
+
* composed by the host. */
|
|
9
|
+
export type DocumentEndpoints = { readonly [K in keyof typeof META.apiRoutes]: ResolvedRoute };
|
|
4
10
|
|
|
5
11
|
export interface MarkdownToolData {
|
|
6
12
|
markdown: string;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import type { ToolPlugin } from "../../tools/types";
|
|
1
|
+
import type { PluginRegistration, ToolPlugin } from "../../tools/types";
|
|
2
2
|
import type { ToolResult } from "gui-chat-protocol";
|
|
3
|
-
import toolDefinition, { TOOL_NAME } from "./definition";
|
|
4
|
-
import
|
|
3
|
+
import toolDefinition, { TOOL_NAME, type DocumentEndpoints, type MarkdownToolData } from "./definition";
|
|
4
|
+
import { pluginEndpoints } from "../api";
|
|
5
|
+
import { wrapWithScope } from "../scope";
|
|
5
6
|
import View from "./View.vue";
|
|
6
7
|
import Preview from "./Preview.vue";
|
|
7
|
-
import {
|
|
8
|
-
import { API_ROUTES } from "../../config/apiRoutes";
|
|
8
|
+
import { apiCall } from "../../utils/api";
|
|
9
9
|
import { makeUuid } from "../../utils/id";
|
|
10
10
|
|
|
11
11
|
const markdownPlugin: ToolPlugin<MarkdownToolData> = {
|
|
12
12
|
toolDefinition,
|
|
13
13
|
|
|
14
14
|
async execute(_context, args) {
|
|
15
|
-
const
|
|
15
|
+
const endpoints = pluginEndpoints<DocumentEndpoints>("markdown");
|
|
16
|
+
const { method, url } = endpoints.create;
|
|
17
|
+
const result = await apiCall<ToolResult<MarkdownToolData>>(url, { method, body: args });
|
|
16
18
|
if (!result.ok) {
|
|
17
19
|
return {
|
|
18
20
|
toolName: TOOL_NAME,
|
|
@@ -29,9 +31,14 @@ const markdownPlugin: ToolPlugin<MarkdownToolData> = {
|
|
|
29
31
|
|
|
30
32
|
isEnabled: () => true,
|
|
31
33
|
generatingMessage: "Creating document...",
|
|
32
|
-
viewComponent: View,
|
|
33
|
-
previewComponent: Preview,
|
|
34
|
+
viewComponent: wrapWithScope("markdown", View),
|
|
35
|
+
previewComponent: wrapWithScope("markdown", Preview),
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export default markdownPlugin;
|
|
37
39
|
export { TOOL_NAME };
|
|
40
|
+
|
|
41
|
+
export const REGISTRATION: PluginRegistration = {
|
|
42
|
+
toolName: TOOL_NAME,
|
|
43
|
+
entry: markdownPlugin,
|
|
44
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { definePluginMeta } from "../meta-types";
|
|
2
|
+
|
|
3
|
+
export const META = definePluginMeta({
|
|
4
|
+
toolName: "presentDocument",
|
|
5
|
+
apiNamespace: "markdown",
|
|
6
|
+
apiRoutes: {
|
|
7
|
+
/** POST /api/markdown — create a new markdown document. */
|
|
8
|
+
create: { method: "POST", path: "" },
|
|
9
|
+
/** PUT /api/markdown/update — overwrite an existing document.
|
|
10
|
+
* Body carries the workspace-relative path so the route doesn't
|
|
11
|
+
* have to reconstruct one from a basename — required after #764
|
|
12
|
+
* sharded artifact storage by YYYY/MM. */
|
|
13
|
+
update: { method: "PUT", path: "/update" },
|
|
14
|
+
},
|
|
15
|
+
mcpDispatch: "create",
|
|
16
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Shape every built-in plugin's `meta.ts` exports as `META`.
|
|
2
|
+
// Host aggregators (src/config/*, server/workspace/paths.ts) iterate
|
|
3
|
+
// over `BUILT_IN_PLUGIN_METAS` and auto-merge the per-dimension
|
|
4
|
+
// records. Plugin-specific literals never appear in host code — the
|
|
5
|
+
// plugin owns what the plugin owns.
|
|
6
|
+
//
|
|
7
|
+
// Browser-safe: no Vue / no Node-only imports. Both server and
|
|
8
|
+
// frontend can import this file (and via it, every plugin's META).
|
|
9
|
+
|
|
10
|
+
/** HTTP methods the route registry recognises. */
|
|
11
|
+
export type RouteMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
12
|
+
|
|
13
|
+
/** A single route the plugin owns. The host composes the full URL
|
|
14
|
+
* as `/api/<apiNamespace><path>`; consumers read both `method` and
|
|
15
|
+
* the resolved `url` from the host registry. */
|
|
16
|
+
export interface RouteSpec {
|
|
17
|
+
readonly method: RouteMethod;
|
|
18
|
+
/** Sub-path under the namespace (`""` = the namespace root,
|
|
19
|
+
* `"/:id"` = a parameterised child). Express path syntax. */
|
|
20
|
+
readonly path: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Resolved route — what the host registry hands back to consumers
|
|
24
|
+
* (server route registration AND client `apiCall`). The plugin
|
|
25
|
+
* declares `{ method, path }`; the host adds the `/api/<ns>` prefix
|
|
26
|
+
* and exposes `{ method, url }`. */
|
|
27
|
+
export interface ResolvedRoute {
|
|
28
|
+
readonly method: RouteMethod;
|
|
29
|
+
/** Full Express path including `/api/<namespace>` prefix. */
|
|
30
|
+
readonly url: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Type-checking helper for a plugin's `meta.ts` literal. The
|
|
34
|
+
* `const` type parameter narrows nested literals (`toolName:
|
|
35
|
+
* "manageX"`, `apiRoutes.foo.path: "/:id"`, …) so host aggregators
|
|
36
|
+
* see the same string-literal types they would with
|
|
37
|
+
* `as const satisfies PluginMeta` — minus the dual annotation
|
|
38
|
+
* noise. Plugin authors write:
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* export const META = definePluginMeta({
|
|
42
|
+
* toolName: "manageX",
|
|
43
|
+
* apiNamespace: "x",
|
|
44
|
+
* apiRoutes: {
|
|
45
|
+
* dispatch: { method: "POST", path: "" },
|
|
46
|
+
* },
|
|
47
|
+
* });
|
|
48
|
+
* ``` */
|
|
49
|
+
export function definePluginMeta<const T extends PluginMeta>(meta: T): T {
|
|
50
|
+
return meta;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A plugin's central-registry-facing metadata. */
|
|
54
|
+
export interface PluginMeta {
|
|
55
|
+
/** MCP tool name string the LLM and JSONL files use. */
|
|
56
|
+
readonly toolName: string;
|
|
57
|
+
/** URL prefix segment under `/api/`. The plugin owns this slug;
|
|
58
|
+
* the host owns the `/api/` prefix. Defaults to `toolName` if
|
|
59
|
+
* omitted but most plugins prefer a shorter slug (e.g.
|
|
60
|
+
* `"accounting"` rather than `"manageAccounting"`).
|
|
61
|
+
*
|
|
62
|
+
* Also doubles as the outer key under `API_ROUTES` — so
|
|
63
|
+
* `API_ROUTES.<apiNamespace>.<routeKey>` is `{ method, url }`. */
|
|
64
|
+
readonly apiNamespace?: string;
|
|
65
|
+
/** Routes owned by this plugin. Each value declares an HTTP
|
|
66
|
+
* method and a sub-path; the host composes the full URL as
|
|
67
|
+
* `/api/<apiNamespace><path>`. */
|
|
68
|
+
readonly apiRoutes?: Readonly<Record<string, RouteSpec>>;
|
|
69
|
+
/** MCP-bridge dispatch route key. Names the entry in `apiRoutes`
|
|
70
|
+
* the MCP server posts tool calls to. Lets `BUILT_IN_SERVER_BINDINGS`
|
|
71
|
+
* derive the binding's URL from META instead of repeating it. */
|
|
72
|
+
readonly mcpDispatch?: string;
|
|
73
|
+
/** Workspace-relative directories owned by this plugin (flat
|
|
74
|
+
* keys). Merged into the central `WORKSPACE_DIRS` so existing
|
|
75
|
+
* call sites (`WORKSPACE_DIRS.accounting`) keep working. */
|
|
76
|
+
readonly workspaceDirs?: Readonly<Record<string, string>>;
|
|
77
|
+
/** Static pubsub channel names owned by this plugin (flat keys).
|
|
78
|
+
* Merged into the central `PUBSUB_CHANNELS`. Channel factories
|
|
79
|
+
* (e.g. `bookChannel(bookId)`) are not part of this map — they
|
|
80
|
+
* live as separate named exports in the plugin's `meta.ts`
|
|
81
|
+
* because their signatures are plugin-specific. */
|
|
82
|
+
readonly staticChannels?: Readonly<Record<string, string>>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Substitute `:param` placeholders in a route URL with caller-
|
|
86
|
+
* provided values. URL-encodes each value so `/foo bar` and
|
|
87
|
+
* `?injection=1` survive intact. */
|
|
88
|
+
export function buildRouteUrl(route: ResolvedRoute, params?: Readonly<Record<string, string | number>>): string {
|
|
89
|
+
if (!params) return route.url;
|
|
90
|
+
let { url } = route;
|
|
91
|
+
for (const [key, value] of Object.entries(params)) {
|
|
92
|
+
const placeholder = `:${key}`;
|
|
93
|
+
if (!url.includes(placeholder)) continue;
|
|
94
|
+
url = url.split(placeholder).join(encodeURIComponent(String(value)));
|
|
95
|
+
}
|
|
96
|
+
return url;
|
|
97
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Vue-free barrel of every built-in plugin's `META` (the central-
|
|
2
|
+
// registry-facing metadata). Host aggregators (src/config/*,
|
|
3
|
+
// server/workspace/paths.ts) iterate over `BUILT_IN_PLUGIN_METAS`
|
|
4
|
+
// and auto-merge per-dimension records — they never hold
|
|
5
|
+
// plugin-specific literals.
|
|
6
|
+
//
|
|
7
|
+
// Why a separate barrel from `src/plugins/index.ts`?
|
|
8
|
+
// `src/plugins/index.ts` exports `BUILT_IN_PLUGINS` (plugin
|
|
9
|
+
// REGISTRATIONS, including Vue View / Preview components). Server
|
|
10
|
+
// code can't import Vue, so this file gives server-side aggregators
|
|
11
|
+
// a Vue-free entry point that still co-locates the per-plugin
|
|
12
|
+
// metadata.
|
|
13
|
+
//
|
|
14
|
+
// **Auto-generated**. The list comes from
|
|
15
|
+
// `_generated/metas.ts` which is rewritten by
|
|
16
|
+
// `scripts/codegen-plugin-barrels.ts` (run via `predev` / `prebuild`
|
|
17
|
+
// hooks). Adding a plugin means dropping a `meta.ts` into the new
|
|
18
|
+
// `src/plugins/<name>/` directory; this file does not need editing.
|
|
19
|
+
|
|
20
|
+
import type { PluginMeta } from "./meta-types";
|
|
21
|
+
import { GENERATED_PLUGIN_METAS } from "./_generated/metas";
|
|
22
|
+
|
|
23
|
+
export const BUILT_IN_PLUGIN_METAS = GENERATED_PLUGIN_METAS;
|
|
24
|
+
export type BuiltInPluginMetas = typeof BUILT_IN_PLUGIN_METAS;
|
|
25
|
+
|
|
26
|
+
// ────────────────────────────────────────────────────────────────
|
|
27
|
+
// Collision detection
|
|
28
|
+
// ────────────────────────────────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// Aggregators spread plugin-owned records into host records. Without
|
|
31
|
+
// a guard, a plugin claiming a host-reserved key (`apiNamespace:
|
|
32
|
+
// "agent"`) or two plugins claiming the same key (both with
|
|
33
|
+
// `workspaceDirs.images`) would silently win the merge and route
|
|
34
|
+
// real traffic to the wrong handler.
|
|
35
|
+
//
|
|
36
|
+
// We don't `throw` at module load — that would brick the whole app
|
|
37
|
+
// for a single buggy plugin (especially relevant once user-installed
|
|
38
|
+
// runtime plugins land). Instead the helpers below are pure,
|
|
39
|
+
// returning collision lists; callers decide the policy:
|
|
40
|
+
//
|
|
41
|
+
// - Built-in aggregators FILTER colliding plugin keys before merge
|
|
42
|
+
// (host wins / first-registered plugin wins).
|
|
43
|
+
// - `server/plugins/diagnostics.ts` collects the lists at boot,
|
|
44
|
+
// surfaces them via `log.warn` + a system notification, and
|
|
45
|
+
// persists them so a UI mounting later can still display them.
|
|
46
|
+
|
|
47
|
+
/** A plugin key colliding with a host-owned key in one aggregator. */
|
|
48
|
+
export interface HostPluginCollision {
|
|
49
|
+
/** Aggregator label (`"API_ROUTES"`, `"WORKSPACE_DIRS"`, …). */
|
|
50
|
+
label: string;
|
|
51
|
+
/** The key claimed by both host and plugin. */
|
|
52
|
+
key: string;
|
|
53
|
+
/** `toolName` of the plugin claiming it. Empty for legacy callers
|
|
54
|
+
* that don't pass per-key plugin attribution. */
|
|
55
|
+
plugin: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Two plugins claiming the same key in the same dimension. */
|
|
59
|
+
export interface IntraPluginCollision {
|
|
60
|
+
/** Which dimension the duplicate appears in. */
|
|
61
|
+
dimension: "toolName" | "apiNamespace" | "workspaceDirs" | "staticChannels";
|
|
62
|
+
/** The duplicated key. */
|
|
63
|
+
key: string;
|
|
64
|
+
/** `toolName`s of the two plugins claiming it (first-registered, second). */
|
|
65
|
+
plugins: [string, string];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Pure check — does any plugin key shadow a host key? Returns the
|
|
69
|
+
* list of colliding keys (empty when clean). Aggregators call this
|
|
70
|
+
* to decide which plugin keys to drop during the merge. */
|
|
71
|
+
export function findHostPluginCollisions(hostRecord: Readonly<Record<string, unknown>>, pluginRecord: Readonly<Record<string, unknown>>): readonly string[] {
|
|
72
|
+
const hostKeys = new Set(Object.keys(hostRecord));
|
|
73
|
+
return Object.keys(pluginRecord).filter((key) => hostKeys.has(key));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Build an attributed collision list — one entry per (key, plugin)
|
|
77
|
+
* pair, where `pluginAttribution[key]` names the plugin claiming
|
|
78
|
+
* that key. Used by aggregators that aggregate ACROSS plugins
|
|
79
|
+
* (workspaceDirs, staticChannels) where each key may come from a
|
|
80
|
+
* different plugin. */
|
|
81
|
+
export function attributeHostPluginCollisions(
|
|
82
|
+
label: string,
|
|
83
|
+
hostRecord: Readonly<Record<string, unknown>>,
|
|
84
|
+
pluginRecord: Readonly<Record<string, unknown>>,
|
|
85
|
+
pluginByKey: Readonly<Record<string, string>>,
|
|
86
|
+
): HostPluginCollision[] {
|
|
87
|
+
return findHostPluginCollisions(hostRecord, pluginRecord).map((key) => ({ label, key, plugin: pluginByKey[key] ?? "" }));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Build a first-write-wins aggregate of a per-plugin record across
|
|
91
|
+
* all plugins. Duplicate keys (= "intra-plugin collision") are
|
|
92
|
+
* reported in the returned `collisions` list with the first-claiming
|
|
93
|
+
* plugin AND the offender; the offender's value is dropped — runtime
|
|
94
|
+
* routes to the first plugin's handler.
|
|
95
|
+
*
|
|
96
|
+
* This is the fix for Codex review iter-3+: previously each
|
|
97
|
+
* aggregator used `Object.fromEntries` / `Object.assign` to merge,
|
|
98
|
+
* which is JS-level last-write-wins. The diagnostic ran AFTER the
|
|
99
|
+
* merge and could only describe what was already lost, with the
|
|
100
|
+
* warning text contradicting actual behavior ("second is ignored"
|
|
101
|
+
* vs runtime's "second wins"). With this builder the merge IS the
|
|
102
|
+
* detection point — first-write semantics are enforced. */
|
|
103
|
+
export function buildPluginAggregate<V>(
|
|
104
|
+
metas: readonly PluginMeta[],
|
|
105
|
+
extract: (meta: PluginMeta) => Readonly<Record<string, V>> | undefined,
|
|
106
|
+
dimension: IntraPluginCollision["dimension"],
|
|
107
|
+
): { aggregate: Record<string, V>; owner: Record<string, string>; collisions: IntraPluginCollision[] } {
|
|
108
|
+
const aggregate: Record<string, V> = {};
|
|
109
|
+
const owner: Record<string, string> = {};
|
|
110
|
+
const collisions: IntraPluginCollision[] = [];
|
|
111
|
+
for (const meta of metas) {
|
|
112
|
+
const record = extract(meta);
|
|
113
|
+
if (!record) continue;
|
|
114
|
+
for (const [key, value] of Object.entries(record)) {
|
|
115
|
+
const priorPlugin = owner[key];
|
|
116
|
+
if (priorPlugin !== undefined) {
|
|
117
|
+
// Two distinct plugins claim the same key. Keep the first
|
|
118
|
+
// entry; report the offender so diagnostics can warn.
|
|
119
|
+
collisions.push({ dimension, key, plugins: [priorPlugin, meta.toolName] });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
aggregate[key] = value;
|
|
123
|
+
owner[key] = meta.toolName;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { aggregate, owner, collisions };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Filter a plugin record so only the keys that survive the merge
|
|
130
|
+
* policy remain: keys not claimed by the host. Returns the cleaned
|
|
131
|
+
* record AND the list of (label, key, plugin) drops so diagnostics
|
|
132
|
+
* can report them.
|
|
133
|
+
*
|
|
134
|
+
* Intra-plugin collisions are filtered EARLIER by
|
|
135
|
+
* `buildPluginAggregate`; by the time this function runs the input
|
|
136
|
+
* is already first-write-wins-clean. */
|
|
137
|
+
export function filterPluginKeys<V>(
|
|
138
|
+
label: string,
|
|
139
|
+
hostKeys: ReadonlySet<string>,
|
|
140
|
+
pluginRecord: Readonly<Record<string, V>>,
|
|
141
|
+
pluginByKey: Readonly<Record<string, string>>,
|
|
142
|
+
): { cleaned: Record<string, V>; dropped: HostPluginCollision[] } {
|
|
143
|
+
const cleaned: Record<string, V> = {};
|
|
144
|
+
const dropped: HostPluginCollision[] = [];
|
|
145
|
+
for (const [key, value] of Object.entries(pluginRecord)) {
|
|
146
|
+
if (hostKeys.has(key)) {
|
|
147
|
+
dropped.push({ label, key, plugin: pluginByKey[key] ?? "" });
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
cleaned[key] = value;
|
|
151
|
+
}
|
|
152
|
+
return { cleaned, dropped };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ────────────────────────────────────────────────────────────────
|
|
156
|
+
// Host aggregate facade
|
|
157
|
+
// ────────────────────────────────────────────────────────────────
|
|
158
|
+
//
|
|
159
|
+
// `buildPluginAggregate` + `filterPluginKeys` always come paired:
|
|
160
|
+
// gather plugin contributions, drop the ones that collide with host
|
|
161
|
+
// keys, spread into the host record. Each of the four aggregator
|
|
162
|
+
// files (toolNames, apiRoutes, pubsubChannels, paths.ts) was repeating
|
|
163
|
+
// the same ~25-line shape. `defineHostAggregate` collapses the
|
|
164
|
+
// runtime half to one call so a new dimension (e.g. featureFlags) is
|
|
165
|
+
// one line at the call site, not five copy-pastes.
|
|
166
|
+
//
|
|
167
|
+
// The TYPE-LEVEL mapped shape stays at the call site — each
|
|
168
|
+
// aggregator's literal-preserving type (`PluginWorkspaceDirsMap`,
|
|
169
|
+
// `PluginApiRoutesMap`, …) is dimension-specific and would lose
|
|
170
|
+
// fidelity if collapsed into a generic helper. The runtime helper
|
|
171
|
+
// returns `Record<string, V>` and the call site narrows it.
|
|
172
|
+
|
|
173
|
+
export interface HostAggregateOptions<V> {
|
|
174
|
+
/** Aggregator label used in `HostPluginCollision.label` (e.g.
|
|
175
|
+
* `"TOOL_NAMES"`, `"API_ROUTES"`). */
|
|
176
|
+
readonly label: string;
|
|
177
|
+
/** Host-owned record. Plugin keys that collide with these are
|
|
178
|
+
* dropped from the merge (host wins) and reported in
|
|
179
|
+
* `hostCollisions`. */
|
|
180
|
+
readonly hostRecord: Readonly<Record<string, V>>;
|
|
181
|
+
/** Per-plugin extractor — returns the contribution this plugin
|
|
182
|
+
* makes to this dimension, or `undefined` to skip. */
|
|
183
|
+
readonly extract: (meta: PluginMeta) => Readonly<Record<string, V>> | undefined;
|
|
184
|
+
/** Dimension label tagged onto every emitted
|
|
185
|
+
* `IntraPluginCollision`. */
|
|
186
|
+
readonly dimension: IntraPluginCollision["dimension"];
|
|
187
|
+
/** Extra reserved keys to drop on collision, ON TOP OF
|
|
188
|
+
* `Object.keys(hostRecord)`. Used when a sibling map shares a
|
|
189
|
+
* namespace with this aggregate — e.g. `WORKSPACE_DIRS` reserves
|
|
190
|
+
* `WORKSPACE_FILES` keys so a plugin can't smuggle in
|
|
191
|
+
* `workspaceDirs: { memory: ... }` and silently disagree with
|
|
192
|
+
* `WORKSPACE_FILES.memory` (CR review #1125). The reserved keys
|
|
193
|
+
* are NOT added to `merged` — only used for collision filtering. */
|
|
194
|
+
readonly additionalReservedKeys?: ReadonlySet<string>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface HostAggregate<V> {
|
|
198
|
+
/** Merged record: host fields plus every plugin contribution that
|
|
199
|
+
* survived collision filtering. */
|
|
200
|
+
readonly merged: Record<string, V>;
|
|
201
|
+
/** Plugin keys dropped because a host record claimed the same
|
|
202
|
+
* key. */
|
|
203
|
+
readonly hostCollisions: readonly HostPluginCollision[];
|
|
204
|
+
/** Two plugins claiming the same key — the second is dropped
|
|
205
|
+
* (first-write-wins) and reported here. */
|
|
206
|
+
readonly intraCollisions: readonly IntraPluginCollision[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Run the standard "build per-plugin aggregate, drop host
|
|
210
|
+
* collisions, spread into host record" pipeline in one call. The
|
|
211
|
+
* caller still owns the literal-preserving type cast on `merged`. */
|
|
212
|
+
export function defineHostAggregate<V>(metas: readonly PluginMeta[], opts: HostAggregateOptions<V>): HostAggregate<V> {
|
|
213
|
+
const { aggregate, owner, collisions } = buildPluginAggregate(metas, opts.extract, opts.dimension);
|
|
214
|
+
const reserved = new Set<string>(Object.keys(opts.hostRecord));
|
|
215
|
+
if (opts.additionalReservedKeys) {
|
|
216
|
+
for (const key of opts.additionalReservedKeys) reserved.add(key);
|
|
217
|
+
}
|
|
218
|
+
const { cleaned, dropped } = filterPluginKeys(opts.label, reserved, aggregate, owner);
|
|
219
|
+
return {
|
|
220
|
+
merged: { ...opts.hostRecord, ...cleaned },
|
|
221
|
+
hostCollisions: dropped,
|
|
222
|
+
intraCollisions: collisions,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-full h-full flex flex-col items-center justify-center p-4 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg border-2 border-gray-200">
|
|
3
3
|
<div class="text-center">
|
|
4
|
-
<!-- Form Icon -->
|
|
5
4
|
<svg class="w-12 h-12 mx-auto mb-3 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
6
5
|
<path
|
|
7
6
|
stroke-linecap="round"
|
|
@@ -11,15 +10,12 @@
|
|
|
11
10
|
/>
|
|
12
11
|
</svg>
|
|
13
12
|
|
|
14
|
-
<!-- Form Title or Default -->
|
|
15
13
|
<h3 class="text-gray-900 font-bold text-lg mb-1 line-clamp-2">
|
|
16
14
|
{{ formData?.title || t("pluginPresentForm.fallbackTitle") }}
|
|
17
15
|
</h3>
|
|
18
16
|
|
|
19
|
-
<!-- Field Count -->
|
|
20
17
|
<p class="text-gray-600 text-sm mb-2">{{ t("pluginPresentForm.fieldCount", { count: fieldCount }, fieldCount) }}</p>
|
|
21
18
|
|
|
22
|
-
<!-- Completion Status -->
|
|
23
19
|
<div v-if="!isSubmitted" class="flex items-center justify-center gap-2">
|
|
24
20
|
<div class="w-32 h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
25
21
|
<div class="h-full bg-blue-600 transition-all duration-300" :style="{ width: `${completionPercentage}%` }" />
|
|
@@ -27,7 +23,6 @@
|
|
|
27
23
|
<span class="text-xs text-gray-500">{{ `${completionPercentage}%` }}</span>
|
|
28
24
|
</div>
|
|
29
25
|
|
|
30
|
-
<!-- Submitted Badge -->
|
|
31
26
|
<div v-else class="inline-flex items-center gap-1 px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs font-semibold">
|
|
32
27
|
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
|
33
28
|
<path
|
|
@@ -62,22 +57,16 @@ const props = defineProps<{
|
|
|
62
57
|
|
|
63
58
|
const formData = computed<FormData | null>(() => {
|
|
64
59
|
if (props.result?.toolName === "presentForm") {
|
|
65
|
-
return props.result.jsonData as FormData;
|
|
60
|
+
return (props.result.data ?? props.result.jsonData) as FormData;
|
|
66
61
|
}
|
|
67
62
|
return null;
|
|
68
63
|
});
|
|
69
64
|
|
|
70
|
-
const viewState = computed<FormViewState | null>(() =>
|
|
71
|
-
return (props.result?.viewState as unknown as FormViewState) || null;
|
|
72
|
-
});
|
|
65
|
+
const viewState = computed<FormViewState | null>(() => (props.result?.viewState as unknown as FormViewState) || null);
|
|
73
66
|
|
|
74
|
-
const fieldCount = computed(() =>
|
|
75
|
-
return formData.value?.fields.length || 0;
|
|
76
|
-
});
|
|
67
|
+
const fieldCount = computed(() => formData.value?.fields.length || 0);
|
|
77
68
|
|
|
78
|
-
const isSubmitted = computed(() =>
|
|
79
|
-
return viewState.value?.submitted || false;
|
|
80
|
-
});
|
|
69
|
+
const isSubmitted = computed(() => viewState.value?.submitted || false);
|
|
81
70
|
|
|
82
71
|
const completionPercentage = computed(() => {
|
|
83
72
|
if (!formData.value || isSubmitted.value) return 100;
|