mulmoclaude 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Scope-provider wrapper for runtime plugin Vue components (#1110).
|
|
3
|
+
// Builds a per-plugin BrowserPluginRuntime and `provide`s it under
|
|
4
|
+
// PLUGIN_RUNTIME_KEY, so plugin descendants can pull it via
|
|
5
|
+
// `useRuntime()` from `gui-chat-protocol/vue`.
|
|
6
|
+
//
|
|
7
|
+
// One instance per mounted plugin component subtree. Mount this in
|
|
8
|
+
// the host's runtime plugin loader (the place that dynamically
|
|
9
|
+
// imports the plugin's `dist/vue.js` and instantiates its
|
|
10
|
+
// `viewComponent` / `previewComponent`).
|
|
11
|
+
//
|
|
12
|
+
// Doubles as the per-plugin error boundary: a Vue `errorCaptured`
|
|
13
|
+
// hook catches uncaught errors thrown during the plugin subtree's
|
|
14
|
+
// render / setup / lifecycle and renders a fallback panel instead
|
|
15
|
+
// of letting the exception break out and unmount the whole chat
|
|
16
|
+
// canvas. One bad plugin should fail in place, not take the host
|
|
17
|
+
// with it. The captured error is logged to the console; the user
|
|
18
|
+
// sees a "crashed" panel with optional stack details and a Retry
|
|
19
|
+
// button that re-mounts the plugin.
|
|
20
|
+
|
|
21
|
+
import { onErrorCaptured, provide } from "vue";
|
|
22
|
+
import { useI18n } from "vue-i18n";
|
|
23
|
+
import { PLUGIN_RUNTIME_KEY } from "gui-chat-protocol/vue";
|
|
24
|
+
import { makeBrowserPluginRuntime } from "../utils/plugin/runtime";
|
|
25
|
+
import { usePluginErrorBoundary } from "../composables/usePluginErrorBoundary";
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
/** npm package name of the plugin whose subtree we're scoping. */
|
|
29
|
+
pkgName: string;
|
|
30
|
+
/** Optional URL map exposed to the plugin via `runtime.endpoints`.
|
|
31
|
+
* Multi-URL built-in plugins (todos, scheduler, mulmoScript, …)
|
|
32
|
+
* pass their endpoint group ({ method, url } records since #1141);
|
|
33
|
+
* runtime-loaded plugins (the common single-dispatch shape) omit
|
|
34
|
+
* this AND host-shared scopes (`files`, `imageStore`, `mcpTools`)
|
|
35
|
+
* pass plain string URLs. Treated opaquely here — each consumer
|
|
36
|
+
* asserts the shape it expects via `pluginEndpoints<E>(scope)`.
|
|
37
|
+
* Contract: `gui-chat-protocol@>=0.3.1`. */
|
|
38
|
+
endpoints?: Readonly<Record<string, unknown>>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const props = defineProps<Props>();
|
|
42
|
+
|
|
43
|
+
const { t } = useI18n();
|
|
44
|
+
|
|
45
|
+
// Construct once — `pkgName` and `endpoints` are fixed for the
|
|
46
|
+
// lifetime of a mounted plugin. If the host needs to rotate the
|
|
47
|
+
// scope (rare), it should remount the entire wrapper rather than
|
|
48
|
+
// mutate either prop.
|
|
49
|
+
const runtime = makeBrowserPluginRuntime({ pkgName: props.pkgName, endpoints: props.endpoints });
|
|
50
|
+
provide(PLUGIN_RUNTIME_KEY, runtime);
|
|
51
|
+
|
|
52
|
+
// ── Error boundary state ────────────────────────────────────────
|
|
53
|
+
//
|
|
54
|
+
// State + reset logic live in `usePluginErrorBoundary` (testable
|
|
55
|
+
// without a DOM). The Vue lifecycle hook `onErrorCaptured` itself
|
|
56
|
+
// must be wired here — the composable can't call `setup()`-only
|
|
57
|
+
// hooks. Returning `false` from the hook tells Vue we've handled
|
|
58
|
+
// the error; without it, the exception propagates to the parent
|
|
59
|
+
// and ultimately to the global error handler.
|
|
60
|
+
const { error, showDetails, mountKey, errorDetails, captureError, retry } = usePluginErrorBoundary(props.pkgName);
|
|
61
|
+
onErrorCaptured((err) => {
|
|
62
|
+
captureError(err);
|
|
63
|
+
return false;
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<div v-if="error" class="rounded border border-red-200 bg-red-50 p-3 text-sm" data-testid="plugin-error-boundary" role="alert">
|
|
69
|
+
<div class="flex items-center gap-2 mb-1">
|
|
70
|
+
<span class="material-icons text-red-500" aria-hidden="true">error_outline</span>
|
|
71
|
+
<span class="font-medium text-red-800">{{ t("pluginErrorBoundary.title", { pkg: pkgName }) }}</span>
|
|
72
|
+
</div>
|
|
73
|
+
<p class="text-red-700 mb-2">{{ t("pluginErrorBoundary.subtitle") }}</p>
|
|
74
|
+
<div class="flex items-center gap-3">
|
|
75
|
+
<button type="button" class="text-xs text-red-600 hover:underline" data-testid="plugin-error-toggle-details" @click="showDetails = !showDetails">
|
|
76
|
+
{{ showDetails ? t("pluginErrorBoundary.hideDetails") : t("pluginErrorBoundary.showDetails") }}
|
|
77
|
+
</button>
|
|
78
|
+
<button type="button" class="text-xs text-red-600 hover:underline" data-testid="plugin-error-retry" @click="retry">
|
|
79
|
+
{{ t("pluginErrorBoundary.retry") }}
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
<pre v-if="showDetails" class="mt-2 text-xs text-red-900 bg-red-100 p-2 rounded overflow-auto max-h-40 whitespace-pre-wrap break-words">{{
|
|
83
|
+
errorDetails
|
|
84
|
+
}}</pre>
|
|
85
|
+
</div>
|
|
86
|
+
<slot v-else :key="mountKey" />
|
|
87
|
+
</template>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
ref="button"
|
|
5
5
|
class="flex-1 h-8 flex items-center gap-1 bg-white border border-gray-300 rounded px-2.5 text-sm text-gray-900 hover:bg-gray-50 text-left"
|
|
6
6
|
data-testid="role-selector-btn"
|
|
7
|
+
:data-role="currentRoleId"
|
|
7
8
|
@click="open = !open"
|
|
8
9
|
>
|
|
9
10
|
<span class="material-icons text-base text-gray-500">{{ roleIcon(roles, currentRoleId) }}</span>
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
</button>
|
|
13
14
|
<div v-if="open" ref="dropdown" class="absolute left-0 right-0 top-full z-50 bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden">
|
|
14
15
|
<button
|
|
15
|
-
v-for="role in
|
|
16
|
+
v-for="role in visibleRoles"
|
|
16
17
|
:key="role.id"
|
|
17
18
|
:data-testid="`role-option-${role.id}`"
|
|
18
19
|
class="w-full flex items-center gap-1.5 px-3 py-1 text-sm text-gray-900 hover:bg-gray-50 text-left"
|
|
@@ -48,6 +49,16 @@ const dropdown = ref<HTMLDivElement | null>(null);
|
|
|
48
49
|
|
|
49
50
|
const currentRoleName = computed(() => roleName(props.roles, props.currentRoleId));
|
|
50
51
|
|
|
52
|
+
// Hide `isDebugRole` entries from the dropdown unless dev mode is
|
|
53
|
+
// on. Set `VITE_DEV_MODE=1` in `.env` to surface them. Existing
|
|
54
|
+
// chat sessions tied to a debug role still render normally elsewhere
|
|
55
|
+
// — this filter only gates the entry point for new sessions.
|
|
56
|
+
const visibleRoles = computed(() => {
|
|
57
|
+
const devMode = import.meta.env.VITE_DEV_MODE === "1";
|
|
58
|
+
if (devMode) return props.roles;
|
|
59
|
+
return props.roles.filter((role) => !role.isDebugRole);
|
|
60
|
+
});
|
|
61
|
+
|
|
51
62
|
function selectRole(roleId: string): void {
|
|
52
63
|
emit("update:currentRoleId", roleId);
|
|
53
64
|
open.value = false;
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full bg-white flex flex-col">
|
|
3
|
+
<div class="flex items-center justify-between gap-2 px-3 py-2 border-b border-gray-100">
|
|
4
|
+
<h2 class="text-lg font-semibold text-gray-800">{{ t("pluginManageRoles.heading") }}</h2>
|
|
5
|
+
<div class="flex items-center gap-2">
|
|
6
|
+
<span class="text-sm text-gray-500">{{ t("pluginManageRoles.roleCount", customRoles.length, { named: { count: customRoles.length } }) }}</span>
|
|
7
|
+
<button
|
|
8
|
+
v-if="!creating"
|
|
9
|
+
data-testid="role-add-btn"
|
|
10
|
+
class="h-8 px-2.5 flex items-center gap-1 text-sm rounded bg-blue-500 text-white hover:bg-blue-600"
|
|
11
|
+
@click="startCreate"
|
|
12
|
+
>
|
|
13
|
+
{{ t("pluginManageRoles.addButton") }}
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="flex-1 overflow-y-auto">
|
|
19
|
+
<!-- New role creation panel -->
|
|
20
|
+
<div v-if="creating" class="m-4 border border-blue-300 bg-blue-50 rounded-lg p-4 space-y-3">
|
|
21
|
+
<div class="text-sm font-semibold text-gray-700">{{ t("pluginManageRoles.createPanel") }}</div>
|
|
22
|
+
|
|
23
|
+
<!-- ID + Name + Icon row -->
|
|
24
|
+
<div class="flex gap-3">
|
|
25
|
+
<div class="w-40">
|
|
26
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldId") }}</label>
|
|
27
|
+
<input
|
|
28
|
+
v-model="newForm.id"
|
|
29
|
+
type="text"
|
|
30
|
+
placeholder="unique-id"
|
|
31
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="flex-1">
|
|
35
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldName") }}</label>
|
|
36
|
+
<input
|
|
37
|
+
v-model="newForm.name"
|
|
38
|
+
type="text"
|
|
39
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="w-32">
|
|
43
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">
|
|
44
|
+
{{ t("pluginManageRoles.fieldIcon") }}
|
|
45
|
+
<a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">{{
|
|
46
|
+
t("pluginManageRoles.helpLink")
|
|
47
|
+
}}</a>
|
|
48
|
+
</label>
|
|
49
|
+
<input
|
|
50
|
+
v-model="newForm.icon"
|
|
51
|
+
type="text"
|
|
52
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Prompt -->
|
|
58
|
+
<div>
|
|
59
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldPrompt") }}</label>
|
|
60
|
+
<textarea
|
|
61
|
+
v-model="newForm.prompt"
|
|
62
|
+
rows="6"
|
|
63
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono resize-y focus:outline-none focus:border-blue-400"
|
|
64
|
+
spellcheck="false"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Plugins -->
|
|
69
|
+
<div>
|
|
70
|
+
<label class="block text-xs font-medium text-gray-600 mb-2">{{ t("pluginManageRoles.fieldPlugins") }}</label>
|
|
71
|
+
<div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
|
|
72
|
+
<label
|
|
73
|
+
v-for="plugin in availablePlugins"
|
|
74
|
+
:key="plugin.name"
|
|
75
|
+
class="flex items-center gap-2 text-sm cursor-pointer"
|
|
76
|
+
:class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
|
|
77
|
+
:title="plugin.enabled ? '' : t('pluginManageRoles.requiresEnv', { env: plugin.requiredEnv.join(', ') })"
|
|
78
|
+
>
|
|
79
|
+
<input
|
|
80
|
+
v-model="newForm.selectedPlugins"
|
|
81
|
+
type="checkbox"
|
|
82
|
+
:value="plugin.name"
|
|
83
|
+
:disabled="!plugin.enabled"
|
|
84
|
+
class="cursor-pointer disabled:cursor-not-allowed"
|
|
85
|
+
/>
|
|
86
|
+
{{ plugin.name }}
|
|
87
|
+
<span v-if="!plugin.enabled" class="text-xs text-gray-400">{{ t("pluginManageRoles.missingEnv", { env: plugin.requiredEnv.join(", ") }) }}</span>
|
|
88
|
+
</label>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- Starter queries -->
|
|
93
|
+
<div>
|
|
94
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">
|
|
95
|
+
{{ t("pluginManageRoles.fieldStarterQueries") }}
|
|
96
|
+
<span class="text-gray-400 font-normal">{{ t("pluginManageRoles.onePerLine") }}</span>
|
|
97
|
+
</label>
|
|
98
|
+
<textarea
|
|
99
|
+
v-model="newForm.queriesText"
|
|
100
|
+
rows="3"
|
|
101
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- Buttons -->
|
|
106
|
+
<div class="flex gap-2 pt-1">
|
|
107
|
+
<button
|
|
108
|
+
class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
109
|
+
:disabled="saving || !!newFormError"
|
|
110
|
+
:title="newFormError ?? ''"
|
|
111
|
+
@click="saveNew"
|
|
112
|
+
>
|
|
113
|
+
{{ saving ? t("pluginManageRoles.creating") : t("pluginManageRoles.create") }}
|
|
114
|
+
</button>
|
|
115
|
+
<button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="cancelCreate">
|
|
116
|
+
{{ t("common.cancel") }}
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
<div v-if="newFormError" class="text-xs text-gray-500" data-testid="role-form-hint">
|
|
120
|
+
{{ newFormError }}
|
|
121
|
+
</div>
|
|
122
|
+
<div v-if="createError" class="text-xs text-red-500">
|
|
123
|
+
{{ createError }}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div v-if="!creating && customRoles.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">
|
|
128
|
+
{{ t("pluginManageRoles.emptyHint") }}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<ul v-if="customRoles.length > 0" class="p-4 space-y-2">
|
|
132
|
+
<li v-for="role in customRoles" :key="role.id" class="rounded-lg border" :class="selectedId === role.id ? 'border-blue-400' : 'border-gray-200'">
|
|
133
|
+
<!-- Role header row -->
|
|
134
|
+
<div
|
|
135
|
+
class="flex items-center gap-3 p-3 cursor-pointer hover:bg-gray-50 rounded-lg"
|
|
136
|
+
:class="selectedId === role.id ? 'rounded-b-none' : ''"
|
|
137
|
+
@click="selectRole(role)"
|
|
138
|
+
>
|
|
139
|
+
<span class="material-icons text-gray-500">{{ role.icon }}</span>
|
|
140
|
+
<div class="flex-1 min-w-0">
|
|
141
|
+
<div class="font-medium text-sm text-gray-800">
|
|
142
|
+
{{ role.name }}
|
|
143
|
+
<span class="ml-1 text-xs font-mono text-gray-400">{{ t("pluginManageRoles.idFormatted", { id: role.id }) }}</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="text-xs text-gray-400 truncate">
|
|
146
|
+
{{ role.availablePlugins.join(", ") }}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<span
|
|
150
|
+
class="material-icons text-gray-400 text-sm"
|
|
151
|
+
:title="selectedId === role.id ? t('pluginManageRoles.collapse') : t('pluginManageRoles.expand')"
|
|
152
|
+
>
|
|
153
|
+
{{ selectedId === role.id ? "expand_less" : "expand_more" }}
|
|
154
|
+
</span>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<!-- Inline editor -->
|
|
158
|
+
<div v-if="selectedId === role.id" class="border-t border-blue-100 bg-blue-50 p-4 space-y-3 rounded-b-lg">
|
|
159
|
+
<!-- ID + Name + Icon row -->
|
|
160
|
+
<div class="flex gap-3">
|
|
161
|
+
<div class="w-40">
|
|
162
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldId") }}</label>
|
|
163
|
+
<input
|
|
164
|
+
v-model="editForm.id"
|
|
165
|
+
type="text"
|
|
166
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="flex-1">
|
|
170
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldName") }}</label>
|
|
171
|
+
<input
|
|
172
|
+
v-model="editForm.name"
|
|
173
|
+
type="text"
|
|
174
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
175
|
+
@keydown.enter="saveEdit(role.id)"
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="w-32">
|
|
179
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">
|
|
180
|
+
{{ t("pluginManageRoles.fieldIcon") }}
|
|
181
|
+
<a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">{{
|
|
182
|
+
t("pluginManageRoles.helpLink")
|
|
183
|
+
}}</a>
|
|
184
|
+
</label>
|
|
185
|
+
<input
|
|
186
|
+
v-model="editForm.icon"
|
|
187
|
+
type="text"
|
|
188
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono focus:outline-none focus:border-blue-400"
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<!-- Prompt -->
|
|
194
|
+
<div>
|
|
195
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldPrompt") }}</label>
|
|
196
|
+
<textarea
|
|
197
|
+
v-model="editForm.prompt"
|
|
198
|
+
rows="6"
|
|
199
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded font-mono resize-y focus:outline-none focus:border-blue-400"
|
|
200
|
+
spellcheck="false"
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<!-- Plugins -->
|
|
205
|
+
<div>
|
|
206
|
+
<label class="block text-xs font-medium text-gray-600 mb-2">{{ t("pluginManageRoles.fieldPlugins") }}</label>
|
|
207
|
+
<div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
|
|
208
|
+
<label
|
|
209
|
+
v-for="plugin in availablePlugins"
|
|
210
|
+
:key="plugin.name"
|
|
211
|
+
class="flex items-center gap-2 text-sm cursor-pointer"
|
|
212
|
+
:class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
|
|
213
|
+
:title="plugin.enabled ? '' : t('pluginManageRoles.requiresEnv', { env: plugin.requiredEnv.join(', ') })"
|
|
214
|
+
>
|
|
215
|
+
<input
|
|
216
|
+
v-model="editForm.selectedPlugins"
|
|
217
|
+
type="checkbox"
|
|
218
|
+
:value="plugin.name"
|
|
219
|
+
:disabled="!plugin.enabled"
|
|
220
|
+
class="cursor-pointer disabled:cursor-not-allowed"
|
|
221
|
+
/>
|
|
222
|
+
{{ plugin.name }}
|
|
223
|
+
<span v-if="!plugin.enabled" class="text-xs text-gray-400">{{
|
|
224
|
+
t("pluginManageRoles.missingEnv", { env: plugin.requiredEnv.join(", ") })
|
|
225
|
+
}}</span>
|
|
226
|
+
</label>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<!-- Starter queries -->
|
|
231
|
+
<div>
|
|
232
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">
|
|
233
|
+
{{ t("pluginManageRoles.fieldStarterQueries") }}
|
|
234
|
+
<span class="text-gray-400 font-normal">{{ t("pluginManageRoles.onePerLine") }}</span>
|
|
235
|
+
</label>
|
|
236
|
+
<textarea
|
|
237
|
+
v-model="editForm.queriesText"
|
|
238
|
+
rows="3"
|
|
239
|
+
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<!-- Buttons -->
|
|
244
|
+
<div class="flex items-center justify-between pt-1">
|
|
245
|
+
<div class="flex gap-2">
|
|
246
|
+
<button
|
|
247
|
+
class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
248
|
+
:disabled="saving || !!editFormError"
|
|
249
|
+
:title="editFormError ?? ''"
|
|
250
|
+
@click="saveEdit(role.id)"
|
|
251
|
+
>
|
|
252
|
+
{{ saving ? t("pluginManageRoles.updating") : t("pluginManageRoles.update") }}
|
|
253
|
+
</button>
|
|
254
|
+
<button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">
|
|
255
|
+
{{ t("common.cancel") }}
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
<button
|
|
259
|
+
class="px-3 py-1.5 text-sm rounded border border-red-200 text-red-500 hover:bg-red-50 disabled:opacity-50"
|
|
260
|
+
:disabled="saving"
|
|
261
|
+
@click="deleteRole(role.id)"
|
|
262
|
+
>
|
|
263
|
+
{{ t("pluginManageRoles.delete") }}
|
|
264
|
+
</button>
|
|
265
|
+
</div>
|
|
266
|
+
<div v-if="editFormError" class="text-xs text-gray-500">
|
|
267
|
+
{{ editFormError }}
|
|
268
|
+
</div>
|
|
269
|
+
<div v-if="saveError" class="text-xs text-red-500">
|
|
270
|
+
{{ saveError }}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</li>
|
|
274
|
+
</ul>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</template>
|
|
278
|
+
|
|
279
|
+
<script setup lang="ts">
|
|
280
|
+
import { ref, computed, watch, onMounted } from "vue";
|
|
281
|
+
import { useI18n } from "vue-i18n";
|
|
282
|
+
import { useFreshPluginData } from "../composables/useFreshPluginData";
|
|
283
|
+
import { useAppApi } from "../composables/useAppApi";
|
|
284
|
+
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
285
|
+
import { getAllPluginNames } from "../tools/index";
|
|
286
|
+
import { apiGet, apiPost } from "../utils/api";
|
|
287
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
288
|
+
|
|
289
|
+
// Inlined from the former `src/plugins/manageRoles/index.ts`
|
|
290
|
+
// (deleted alongside the manageRoles MCP tool — #949). RolesView
|
|
291
|
+
// is the only consumer, so co-locating the types here avoids a
|
|
292
|
+
// dangling single-purpose module.
|
|
293
|
+
export interface CustomRole {
|
|
294
|
+
id: string;
|
|
295
|
+
name: string;
|
|
296
|
+
icon: string;
|
|
297
|
+
prompt: string;
|
|
298
|
+
availablePlugins: string[];
|
|
299
|
+
queries?: string[];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface ManageRolesData {
|
|
303
|
+
customRoles: CustomRole[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const { t } = useI18n();
|
|
307
|
+
|
|
308
|
+
interface PluginEntry {
|
|
309
|
+
name: string;
|
|
310
|
+
enabled: boolean;
|
|
311
|
+
requiredEnv: string[];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Plugins the user can assign — exclude internal/auto-managed ones
|
|
315
|
+
const EXCLUDED = new Set(["text-response"]);
|
|
316
|
+
const guiPlugins: PluginEntry[] = getAllPluginNames()
|
|
317
|
+
.filter((name) => !EXCLUDED.has(name))
|
|
318
|
+
.map((name) => ({ name, enabled: true, requiredEnv: [] }));
|
|
319
|
+
|
|
320
|
+
const availablePlugins = ref<PluginEntry[]>(guiPlugins);
|
|
321
|
+
|
|
322
|
+
onMounted(async () => {
|
|
323
|
+
const result = await apiGet<PluginEntry[]>(API_ROUTES.mcpTools.list);
|
|
324
|
+
if (result.ok) {
|
|
325
|
+
availablePlugins.value = [...guiPlugins, ...result.data];
|
|
326
|
+
}
|
|
327
|
+
// Non-critical: MCP tools enrich the plugin palette for role editing
|
|
328
|
+
// but the view works fine with GUI plugins alone. No error banner needed.
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const props = defineProps<{
|
|
332
|
+
selectedResult?: ToolResultComplete<ManageRolesData>;
|
|
333
|
+
}>();
|
|
334
|
+
const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
|
|
335
|
+
|
|
336
|
+
const appApi = useAppApi();
|
|
337
|
+
|
|
338
|
+
const customRoles = ref<CustomRole[]>(props.selectedResult?.data?.customRoles ?? []);
|
|
339
|
+
|
|
340
|
+
const { refresh: refreshCustomRoles } = useFreshPluginData<CustomRole[]>({
|
|
341
|
+
endpoint: () => API_ROUTES.roles.list,
|
|
342
|
+
extract: (json) => (Array.isArray(json) ? (json as CustomRole[]) : null),
|
|
343
|
+
apply: (data) => {
|
|
344
|
+
customRoles.value = data;
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
watch(
|
|
349
|
+
() => props.selectedResult?.uuid,
|
|
350
|
+
() => {
|
|
351
|
+
customRoles.value = props.selectedResult?.data?.customRoles ?? [];
|
|
352
|
+
void refreshCustomRoles();
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// ── Selection & edit form ─────────────────────────────────────────────────────
|
|
357
|
+
|
|
358
|
+
const selectedId = ref<string | null>(null);
|
|
359
|
+
const saving = ref(false);
|
|
360
|
+
const saveError = ref("");
|
|
361
|
+
|
|
362
|
+
interface EditForm {
|
|
363
|
+
id: string;
|
|
364
|
+
name: string;
|
|
365
|
+
icon: string;
|
|
366
|
+
prompt: string;
|
|
367
|
+
selectedPlugins: string[];
|
|
368
|
+
queriesText: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const editForm = ref<EditForm>({
|
|
372
|
+
id: "",
|
|
373
|
+
name: "",
|
|
374
|
+
icon: "",
|
|
375
|
+
prompt: "",
|
|
376
|
+
selectedPlugins: [],
|
|
377
|
+
queriesText: "",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const creating = ref(false);
|
|
381
|
+
const createError = ref("");
|
|
382
|
+
const newForm = ref<EditForm>({
|
|
383
|
+
id: "",
|
|
384
|
+
name: "",
|
|
385
|
+
icon: "person",
|
|
386
|
+
prompt: "",
|
|
387
|
+
selectedPlugins: [],
|
|
388
|
+
queriesText: "",
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
function startCreate() {
|
|
392
|
+
selectedId.value = null;
|
|
393
|
+
createError.value = "";
|
|
394
|
+
newForm.value = {
|
|
395
|
+
id: "",
|
|
396
|
+
name: "",
|
|
397
|
+
icon: "person",
|
|
398
|
+
prompt: "",
|
|
399
|
+
selectedPlugins: [],
|
|
400
|
+
queriesText: "",
|
|
401
|
+
};
|
|
402
|
+
creating.value = true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function cancelCreate() {
|
|
406
|
+
creating.value = false;
|
|
407
|
+
createError.value = "";
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function selectRole(role: CustomRole) {
|
|
411
|
+
if (selectedId.value === role.id) {
|
|
412
|
+
selectedId.value = null;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
selectedId.value = role.id;
|
|
416
|
+
saveError.value = "";
|
|
417
|
+
editForm.value = {
|
|
418
|
+
id: role.id,
|
|
419
|
+
name: role.name,
|
|
420
|
+
icon: role.icon,
|
|
421
|
+
prompt: role.prompt,
|
|
422
|
+
selectedPlugins: [...role.availablePlugins],
|
|
423
|
+
queriesText: (role.queries ?? []).join("\n"),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ── API ───────────────────────────────────────────────────────────────────────
|
|
428
|
+
|
|
429
|
+
interface ManageResult {
|
|
430
|
+
success?: boolean;
|
|
431
|
+
error?: string;
|
|
432
|
+
[key: string]: unknown;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function callManage(body: Record<string, unknown>): Promise<ManageResult> {
|
|
436
|
+
const result = await apiPost<ManageResult>(API_ROUTES.roles.manage, body);
|
|
437
|
+
if (!result.ok) {
|
|
438
|
+
// Prefer the backend's error message (e.g. validation failure
|
|
439
|
+
// details). Fall back to a status code only when the server didn't
|
|
440
|
+
// give us anything useful.
|
|
441
|
+
return {
|
|
442
|
+
success: false,
|
|
443
|
+
error:
|
|
444
|
+
result.status === 0
|
|
445
|
+
? result.error || t("pluginManageRoles.errNetworkError")
|
|
446
|
+
: result.error || t("pluginManageRoles.errServerError", { status: result.status }),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return result.data;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function refreshList() {
|
|
453
|
+
const result = await callManage({ action: "list" });
|
|
454
|
+
if (result.success) {
|
|
455
|
+
const data = result as { data?: { customRoles?: CustomRole[] } };
|
|
456
|
+
customRoles.value = data.data?.customRoles ?? [];
|
|
457
|
+
if (props.selectedResult) {
|
|
458
|
+
emit("updateResult", {
|
|
459
|
+
...props.selectedResult,
|
|
460
|
+
...result,
|
|
461
|
+
uuid: props.selectedResult.uuid,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
// Let App.vue know the dropdown needs to refresh.
|
|
465
|
+
await Promise.resolve(appApi.refreshRoles());
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function validateRoleForm(form: EditForm, excludeId: string | null): string | null {
|
|
470
|
+
const trimmedId = form.id.trim();
|
|
471
|
+
const trimmedName = form.name.trim();
|
|
472
|
+
if (!trimmedId) return t("pluginManageRoles.errIdRequired");
|
|
473
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(trimmedId)) {
|
|
474
|
+
return t("pluginManageRoles.errIdInvalid");
|
|
475
|
+
}
|
|
476
|
+
if (!trimmedName) return t("pluginManageRoles.errNameRequired");
|
|
477
|
+
if (customRoles.value.some((existing) => existing.id === trimmedId && existing.id !== excludeId)) {
|
|
478
|
+
return t("pluginManageRoles.errIdDuplicate", { id: trimmedId });
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const newFormError = computed<string | null>(() => validateRoleForm(newForm.value, null));
|
|
484
|
+
|
|
485
|
+
const editFormError = computed<string | null>(() => validateRoleForm(editForm.value, selectedId.value));
|
|
486
|
+
|
|
487
|
+
function buildNewRole(): CustomRole {
|
|
488
|
+
return {
|
|
489
|
+
id: newForm.value.id.trim(),
|
|
490
|
+
name: newForm.value.name.trim(),
|
|
491
|
+
icon: newForm.value.icon.trim() || "person",
|
|
492
|
+
prompt: newForm.value.prompt,
|
|
493
|
+
availablePlugins: newForm.value.selectedPlugins,
|
|
494
|
+
queries: newForm.value.queriesText
|
|
495
|
+
.split("\n")
|
|
496
|
+
.map((line) => line.trim())
|
|
497
|
+
.filter(Boolean),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function saveNew() {
|
|
502
|
+
if (newFormError.value) {
|
|
503
|
+
createError.value = newFormError.value;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
saving.value = true;
|
|
507
|
+
createError.value = "";
|
|
508
|
+
const result = await callManage({ action: "create", role: buildNewRole() });
|
|
509
|
+
if (result.success) {
|
|
510
|
+
creating.value = false;
|
|
511
|
+
await refreshList();
|
|
512
|
+
} else {
|
|
513
|
+
createError.value = result.error ?? t("pluginManageRoles.errCreateFailed");
|
|
514
|
+
}
|
|
515
|
+
saving.value = false;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function saveEdit(originalId: string) {
|
|
519
|
+
if (editFormError.value) {
|
|
520
|
+
saveError.value = editFormError.value;
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
saving.value = true;
|
|
524
|
+
saveError.value = "";
|
|
525
|
+
const role: CustomRole = {
|
|
526
|
+
id: editForm.value.id.trim(),
|
|
527
|
+
name: editForm.value.name.trim(),
|
|
528
|
+
icon: editForm.value.icon.trim(),
|
|
529
|
+
prompt: editForm.value.prompt,
|
|
530
|
+
availablePlugins: editForm.value.selectedPlugins,
|
|
531
|
+
queries: editForm.value.queriesText
|
|
532
|
+
.split("\n")
|
|
533
|
+
.map((line) => line.trim())
|
|
534
|
+
.filter(Boolean),
|
|
535
|
+
};
|
|
536
|
+
const result = await callManage({
|
|
537
|
+
action: "update",
|
|
538
|
+
role,
|
|
539
|
+
oldRoleId: originalId,
|
|
540
|
+
});
|
|
541
|
+
if (result.success) {
|
|
542
|
+
selectedId.value = null;
|
|
543
|
+
await refreshList();
|
|
544
|
+
} else {
|
|
545
|
+
saveError.value = result.error ?? t("pluginManageRoles.errSaveFailed");
|
|
546
|
+
}
|
|
547
|
+
saving.value = false;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function deleteRole(roleId: string) {
|
|
551
|
+
saving.value = true;
|
|
552
|
+
saveError.value = "";
|
|
553
|
+
const result = await callManage({ action: "delete", roleId });
|
|
554
|
+
if (result.success) {
|
|
555
|
+
selectedId.value = null;
|
|
556
|
+
await refreshList();
|
|
557
|
+
} else {
|
|
558
|
+
saveError.value = result.error ?? t("pluginManageRoles.errDeleteFailed");
|
|
559
|
+
}
|
|
560
|
+
saving.value = false;
|
|
561
|
+
}
|
|
562
|
+
</script>
|