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
|
@@ -12,8 +12,11 @@ import { fillMarkdownImagePlaceholders } from "../../utils/files/markdown-image-
|
|
|
12
12
|
import { saveMarkdown, overwriteMarkdown, isMarkdownPath } from "../../utils/files/markdown-store.js";
|
|
13
13
|
import { saveSpreadsheet, overwriteSpreadsheet, isSpreadsheetPath } from "../../utils/files/spreadsheet-store.js";
|
|
14
14
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
15
|
+
import { bindRoute } from "../../utils/router.js";
|
|
16
|
+
import { collectPluginMetaDiagnostics } from "../../plugins/diagnostics.js";
|
|
15
17
|
import { log } from "../../system/logger/index.js";
|
|
16
18
|
import { previewSnippet } from "../../utils/logPreview.js";
|
|
19
|
+
import { publishFileChange } from "../../events/file-change.js";
|
|
17
20
|
|
|
18
21
|
const router = Router();
|
|
19
22
|
|
|
@@ -66,6 +69,7 @@ interface PresentDocumentBody {
|
|
|
66
69
|
|
|
67
70
|
interface PresentDocumentSuccess {
|
|
68
71
|
message: string;
|
|
72
|
+
instructions: string;
|
|
69
73
|
title: string;
|
|
70
74
|
data: { markdown: string; filenamePrefix: string };
|
|
71
75
|
}
|
|
@@ -74,8 +78,9 @@ interface PresentDocumentError {
|
|
|
74
78
|
error: string;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
bindRoute(
|
|
82
|
+
router,
|
|
83
|
+
API_ROUTES.markdown.create,
|
|
79
84
|
async (req: Request<object, unknown, PresentDocumentBody>, res: Response<PresentDocumentSuccess | PresentDocumentError>) => {
|
|
80
85
|
const { title, markdown, filenamePrefix } = req.body;
|
|
81
86
|
log.info("plugins", "presentDocument: start", {
|
|
@@ -92,7 +97,8 @@ router.post(
|
|
|
92
97
|
const markdownPath = await saveMarkdown(filledMarkdown, filenamePrefix);
|
|
93
98
|
log.info("plugins", "presentDocument: ok", { markdownPath, bytes: filledMarkdown.length });
|
|
94
99
|
res.json({
|
|
95
|
-
message: `
|
|
100
|
+
message: `Saved markdown to ${markdownPath}`,
|
|
101
|
+
instructions: "Acknowledge that the document has been presented to the user.",
|
|
96
102
|
title,
|
|
97
103
|
data: { markdown: markdownPath, filenamePrefix },
|
|
98
104
|
});
|
|
@@ -117,8 +123,9 @@ interface UpdateMarkdownError {
|
|
|
117
123
|
error: string;
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
bindRoute(
|
|
127
|
+
router,
|
|
128
|
+
API_ROUTES.markdown.update,
|
|
122
129
|
async (req: Request<object, unknown, UpdateMarkdownBody>, res: Response<UpdateMarkdownResponse | UpdateMarkdownError>) => {
|
|
123
130
|
const { relativePath, markdown } = req.body;
|
|
124
131
|
log.info("plugins", "updateMarkdown: start", {
|
|
@@ -140,6 +147,7 @@ router.put(
|
|
|
140
147
|
try {
|
|
141
148
|
await overwriteMarkdown(relativePath, markdown);
|
|
142
149
|
log.info("plugins", "updateMarkdown: ok", { pathPreview: previewSnippet(relativePath), bytes: markdown.length });
|
|
150
|
+
void publishFileChange(relativePath);
|
|
143
151
|
res.json({ path: relativePath });
|
|
144
152
|
} catch (err) {
|
|
145
153
|
log.error("plugins", "updateMarkdown: threw", { pathPreview: previewSnippet(relativePath), error: errorMessage(err) });
|
|
@@ -156,8 +164,9 @@ router.put(
|
|
|
156
164
|
// than fabricating a fake context.
|
|
157
165
|
|
|
158
166
|
// presentSpreadsheet — validate, then save sheets to disk
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
bindRoute(
|
|
168
|
+
router,
|
|
169
|
+
API_ROUTES.spreadsheet.create,
|
|
161
170
|
wrapPluginExecute<SpreadsheetArgs, unknown>(async (req) => {
|
|
162
171
|
const result = await executeSpreadsheet(req.body);
|
|
163
172
|
if (!Array.isArray(result.data.sheets)) {
|
|
@@ -184,8 +193,9 @@ interface UpdateSpreadsheetError {
|
|
|
184
193
|
error: string;
|
|
185
194
|
}
|
|
186
195
|
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
bindRoute(
|
|
197
|
+
router,
|
|
198
|
+
API_ROUTES.spreadsheet.update,
|
|
189
199
|
async (req: Request<object, unknown, UpdateSpreadsheetBody>, res: Response<UpdateSpreadsheetResponse | UpdateSpreadsheetError>) => {
|
|
190
200
|
const { relativePath, sheets } = req.body;
|
|
191
201
|
log.info("plugins", "updateSpreadsheet: start", {
|
|
@@ -228,8 +238,9 @@ router.post(
|
|
|
228
238
|
);
|
|
229
239
|
|
|
230
240
|
// presentForm — form
|
|
231
|
-
|
|
232
|
-
|
|
241
|
+
bindRoute(
|
|
242
|
+
router,
|
|
243
|
+
API_ROUTES.form.dispatch,
|
|
233
244
|
wrapPluginExecute((req) => executeForm(null as never, req.body)),
|
|
234
245
|
);
|
|
235
246
|
|
|
@@ -240,11 +251,12 @@ router.post(
|
|
|
240
251
|
const BLANK_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
|
|
241
252
|
|
|
242
253
|
// openCanvas — drawing canvas
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
bindRoute(
|
|
255
|
+
router,
|
|
256
|
+
API_ROUTES.canvas.dispatch,
|
|
245
257
|
wrapPluginExecute(async () => {
|
|
246
258
|
const imagePath = await saveImage(BLANK_PNG_BASE64);
|
|
247
|
-
const base = await executeOpenCanvas();
|
|
259
|
+
const base = await executeOpenCanvas(imagePath);
|
|
248
260
|
return { ...base, data: { imageData: imagePath, prompt: "" } };
|
|
249
261
|
}),
|
|
250
262
|
);
|
|
@@ -255,4 +267,12 @@ router.post(
|
|
|
255
267
|
wrapPluginExecute((req) => executePresent3D(null as never, req.body)),
|
|
256
268
|
);
|
|
257
269
|
|
|
270
|
+
// META aggregator diagnostics — boot-time host/plugin or plugin/plugin
|
|
271
|
+
// key collisions. The frontend fetches this once at mount so a tab
|
|
272
|
+
// that opens after the boot-time `publishNotification` fired still
|
|
273
|
+
// gets the warning. Empty array when clean.
|
|
274
|
+
router.get(API_ROUTES.plugins.diagnostics, (_req, res) => {
|
|
275
|
+
res.json({ diagnostics: collectPluginMetaDiagnostics() });
|
|
276
|
+
});
|
|
277
|
+
|
|
258
278
|
export default router;
|
|
@@ -2,11 +2,14 @@ import { Router, Request, Response } from "express";
|
|
|
2
2
|
import { WORKSPACE_DIRS } from "../../workspace/paths.js";
|
|
3
3
|
import { writeWorkspaceText } from "../../utils/files/workspace-io.js";
|
|
4
4
|
import { buildArtifactPath } from "../../utils/files/naming.js";
|
|
5
|
+
import { overwriteHtml, isHtmlPath } from "../../utils/files/html-store.js";
|
|
5
6
|
import { errorMessage } from "../../utils/errors.js";
|
|
6
7
|
import { badRequest, serverError } from "../../utils/httpError.js";
|
|
7
8
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
9
|
+
import { bindRoute } from "../../utils/router.js";
|
|
8
10
|
import { log } from "../../system/logger/index.js";
|
|
9
11
|
import { previewSnippet } from "../../utils/logPreview.js";
|
|
12
|
+
import { publishFileChange } from "../../events/file-change.js";
|
|
10
13
|
|
|
11
14
|
const router = Router();
|
|
12
15
|
|
|
@@ -18,7 +21,7 @@ interface PresentHtmlBody {
|
|
|
18
21
|
interface PresentHtmlSuccessResponse {
|
|
19
22
|
message: string;
|
|
20
23
|
instructions: string;
|
|
21
|
-
data: {
|
|
24
|
+
data: { title?: string; filePath: string };
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
interface PresentHtmlErrorResponse {
|
|
@@ -27,7 +30,7 @@ interface PresentHtmlErrorResponse {
|
|
|
27
30
|
|
|
28
31
|
type PresentHtmlResponse = PresentHtmlSuccessResponse | PresentHtmlErrorResponse;
|
|
29
32
|
|
|
30
|
-
router
|
|
33
|
+
bindRoute(router, API_ROUTES.html.create, async (req: Request<object, unknown, PresentHtmlBody>, res: Response<PresentHtmlResponse>) => {
|
|
31
34
|
const { html, title } = req.body;
|
|
32
35
|
log.info("html", "present: start", {
|
|
33
36
|
titlePreview: typeof title === "string" ? previewSnippet(title) : undefined,
|
|
@@ -43,10 +46,12 @@ router.post(API_ROUTES.html.present, async (req: Request<object, unknown, Presen
|
|
|
43
46
|
const filePath = buildArtifactPath(WORKSPACE_DIRS.htmls, title, ".html", "page");
|
|
44
47
|
await writeWorkspaceText(filePath, html);
|
|
45
48
|
log.info("html", "present: ok", { filePath, bytes: html.length });
|
|
49
|
+
// Fire-and-forget: any subscribed View tab refetches via cache-bust.
|
|
50
|
+
void publishFileChange(filePath);
|
|
46
51
|
res.json({
|
|
47
52
|
message: `Saved HTML to ${filePath}`,
|
|
48
53
|
instructions: "Acknowledge that the HTML page has been presented to the user.",
|
|
49
|
-
data: {
|
|
54
|
+
data: { title, filePath },
|
|
50
55
|
});
|
|
51
56
|
} catch (err) {
|
|
52
57
|
log.error("html", "present: threw", { error: errorMessage(err) });
|
|
@@ -54,4 +59,54 @@ router.post(API_ROUTES.html.present, async (req: Request<object, unknown, Presen
|
|
|
54
59
|
}
|
|
55
60
|
});
|
|
56
61
|
|
|
62
|
+
// Update html file on disk (user edits in View). Body carries the
|
|
63
|
+
// workspace-relative path verbatim (e.g.
|
|
64
|
+
// `artifacts/html/2026/04/page-abc.html`) so the route doesn't have to
|
|
65
|
+
// reconstruct one from a basename — same shape as presentDocument.updateMarkdown.
|
|
66
|
+
interface UpdateHtmlBody {
|
|
67
|
+
relativePath: string;
|
|
68
|
+
html: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface UpdateHtmlSuccessResponse {
|
|
72
|
+
path: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface UpdateHtmlErrorResponse {
|
|
76
|
+
error: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
bindRoute(
|
|
80
|
+
router,
|
|
81
|
+
API_ROUTES.html.update,
|
|
82
|
+
async (req: Request<object, unknown, UpdateHtmlBody>, res: Response<UpdateHtmlSuccessResponse | UpdateHtmlErrorResponse>) => {
|
|
83
|
+
const { relativePath, html } = req.body;
|
|
84
|
+
log.info("html", "update: start", {
|
|
85
|
+
pathPreview: typeof relativePath === "string" ? previewSnippet(relativePath) : undefined,
|
|
86
|
+
bytes: typeof html === "string" ? html.length : undefined,
|
|
87
|
+
});
|
|
88
|
+
if (!html) {
|
|
89
|
+
log.warn("html", "update: missing html");
|
|
90
|
+
badRequest(res, "html is required");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!relativePath || !isHtmlPath(relativePath)) {
|
|
94
|
+
log.warn("html", "update: invalid relativePath", {
|
|
95
|
+
pathPreview: typeof relativePath === "string" ? previewSnippet(relativePath) : undefined,
|
|
96
|
+
});
|
|
97
|
+
badRequest(res, "invalid html relativePath");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
await overwriteHtml(relativePath, html);
|
|
102
|
+
log.info("html", "update: ok", { pathPreview: previewSnippet(relativePath), bytes: html.length });
|
|
103
|
+
void publishFileChange(relativePath);
|
|
104
|
+
res.json({ path: relativePath });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
log.error("html", "update: threw", { pathPreview: previewSnippet(relativePath), error: errorMessage(err) });
|
|
107
|
+
serverError(res, errorMessage(err));
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
57
112
|
export default router;
|
|
@@ -104,14 +104,7 @@ function saveRoleResult(input: ManageRolesInput, sessionId: string): Record<stri
|
|
|
104
104
|
const { role, isRename } = validated;
|
|
105
105
|
const { action, oldRoleId } = input;
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
const pluginsToSave = role.availablePlugins ?? [];
|
|
109
|
-
const roleToSave = {
|
|
110
|
-
...role,
|
|
111
|
-
availablePlugins: pluginsToSave.filter((plugin) => plugin !== "switchRole"),
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
saveRole(role.id, roleToSave);
|
|
107
|
+
saveRole(role.id, role);
|
|
115
108
|
// On rename, remove the old file even if its id matches a built-in —
|
|
116
109
|
// a file at `config/roles/<builtin>.json` is a user-created override,
|
|
117
110
|
// not the built-in itself (which lives in BUILTIN_ROLES). Leaving it
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// HTTP routes for runtime-loaded plugins (#1043 C-2).
|
|
2
|
+
//
|
|
3
|
+
// GET /api/plugins/runtime/list
|
|
4
|
+
// → { plugins: [{ name, version, toolName, description }, …] }
|
|
5
|
+
//
|
|
6
|
+
// POST /api/plugins/runtime/:pkg/dispatch
|
|
7
|
+
// body: <args> directly — same convention as static plugin
|
|
8
|
+
// endpoints (see server/api/routes/plugins.ts), so
|
|
9
|
+
// mcp-server's generic `postJson(endpoint, args)` works
|
|
10
|
+
// unchanged for runtime plugins.
|
|
11
|
+
// → whatever the plugin's `execute()` returns (forwarded as JSON)
|
|
12
|
+
//
|
|
13
|
+
// GET /api/plugins/runtime/:pkg/oauth/callback?code=&state=&error=
|
|
14
|
+
// Generic OAuth redirect receiver (#1162). Spotify (and any
|
|
15
|
+
// future OAuth-using runtime plugin) registers this URL with
|
|
16
|
+
// its provider's developer dashboard; the browser comes back
|
|
17
|
+
// here after consent. Host extracts `:pkg`, registry-looks-up
|
|
18
|
+
// the plugin, forwards as `kind: "oauthCallback"` dispatch
|
|
19
|
+
// args, and renders the plugin's returned `html` (or a fallback)
|
|
20
|
+
// back to the browser. Bearer-auth-EXEMPT (no Authorization
|
|
21
|
+
// header on a redirect); CSRF defended by the plugin's
|
|
22
|
+
// single-use `state`.
|
|
23
|
+
//
|
|
24
|
+
// GET /api/plugins/runtime/:pkg/:version/*
|
|
25
|
+
// Static-mount of the extracted cache directory; the frontend
|
|
26
|
+
// loader uses this for `import("/api/plugins/runtime/<pkg>/<ver>/dist/vue.js")`.
|
|
27
|
+
//
|
|
28
|
+
// The registry is owned by `server/plugins/runtime-registry.ts` and
|
|
29
|
+
// populated at boot from the install ledger. A 404 from any of these
|
|
30
|
+
// routes means the plugin isn't installed (or failed to load — see
|
|
31
|
+
// boot logs).
|
|
32
|
+
|
|
33
|
+
import { realpathSync } from "node:fs";
|
|
34
|
+
import { Router, type Request, type Response } from "express";
|
|
35
|
+
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
36
|
+
import { getRuntimePluginByOauthAlias, getRuntimePlugins } from "../../plugins/runtime-registry.js";
|
|
37
|
+
import { notFound, serverError } from "../../utils/httpError.js";
|
|
38
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
39
|
+
import { isRecord } from "../../utils/types.js";
|
|
40
|
+
import { resolveWithinRoot } from "../../utils/files/safe.js";
|
|
41
|
+
import { readPluginAsset } from "../../utils/files/plugins-io.js";
|
|
42
|
+
import { log } from "../../system/logger/index.js";
|
|
43
|
+
|
|
44
|
+
const LOG_PREFIX = "api/plugins/runtime";
|
|
45
|
+
|
|
46
|
+
const router = Router();
|
|
47
|
+
|
|
48
|
+
interface ListedPlugin {
|
|
49
|
+
name: string;
|
|
50
|
+
version: string;
|
|
51
|
+
toolName: string;
|
|
52
|
+
description: string;
|
|
53
|
+
/** Absolute URL prefix the frontend uses for static-mount fetches. */
|
|
54
|
+
assetBase: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
router.get(API_ROUTES.plugins.runtimeList, (_req: Request, res: Response<{ plugins: ListedPlugin[] }>) => {
|
|
58
|
+
const plugins = getRuntimePlugins().map<ListedPlugin>((entry) => ({
|
|
59
|
+
name: entry.name,
|
|
60
|
+
version: entry.version,
|
|
61
|
+
toolName: entry.definition.name,
|
|
62
|
+
description: entry.definition.description,
|
|
63
|
+
assetBase: `/api/plugins/runtime/${encodeURIComponent(entry.name)}/${encodeURIComponent(entry.version)}`,
|
|
64
|
+
}));
|
|
65
|
+
res.json({ plugins });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
router.post(API_ROUTES.plugins.runtimeDispatch, async (req: Request<{ pkg: string }>, res: Response) => {
|
|
69
|
+
const pkg = decodeURIComponent(req.params.pkg);
|
|
70
|
+
const plugin = getRuntimePlugins().find((entry) => entry.name === pkg);
|
|
71
|
+
if (!plugin) {
|
|
72
|
+
notFound(res, `runtime plugin "${pkg}" not registered`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!plugin.execute) {
|
|
76
|
+
serverError(res, `runtime plugin "${pkg}" has no execute() — the package's dist/index.js must export a function under "${plugin.definition.name}"`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const args = isRecord(req.body) ? req.body : {};
|
|
80
|
+
try {
|
|
81
|
+
// gui-chat-protocol's ToolPluginCore.execute is
|
|
82
|
+
// `(context: ToolContext, args) => Promise<ToolResult>`. The
|
|
83
|
+
// server has no UI-side state to share, so context is an empty
|
|
84
|
+
// object — but it MUST be the first arg, otherwise the plugin
|
|
85
|
+
// destructures its args from `undefined` and the call fails with
|
|
86
|
+
// "Cannot destructure property '<field>' of '<arg>' as it is
|
|
87
|
+
// undefined".
|
|
88
|
+
const context = {};
|
|
89
|
+
const result = await plugin.execute(context, args);
|
|
90
|
+
// Forward whatever the plugin returns as the response body
|
|
91
|
+
// (mirrors static plugin routes — see plugins.ts). MCP server
|
|
92
|
+
// spreads this into the toolResult event downstream.
|
|
93
|
+
res.json(result);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
log.error(LOG_PREFIX, "execute failed", { pkg, error: errorMessage(err) });
|
|
96
|
+
serverError(res, `plugin execute failed: ${errorMessage(err)}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Generic OAuth callback receiver (#1162). The plugin owns state
|
|
101
|
+
// validation + token exchange — the host's role is just URL routing
|
|
102
|
+
// + HTML response rendering. The plugin returns
|
|
103
|
+
// `{ html?: string; message?: string }` from its `oauthCallback`
|
|
104
|
+
// kind handler; the host renders `html` verbatim if present, else
|
|
105
|
+
// falls back to a minimal default.
|
|
106
|
+
function buildOauthCallbackArgs(query: Request["query"]) {
|
|
107
|
+
const { code, state, error: providerError } = query;
|
|
108
|
+
return {
|
|
109
|
+
kind: "oauthCallback" as const,
|
|
110
|
+
code: typeof code === "string" ? code : undefined,
|
|
111
|
+
state: typeof state === "string" ? state : undefined,
|
|
112
|
+
error: typeof providerError === "string" ? providerError : undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function sendOauthCallbackResult(res: Response, result: unknown): void {
|
|
117
|
+
const html = isRecord(result) && typeof result.html === "string" ? result.html : null;
|
|
118
|
+
const message = isRecord(result) && typeof result.message === "string" ? result.message : "";
|
|
119
|
+
const ok = isRecord(result) && result.ok === true;
|
|
120
|
+
res
|
|
121
|
+
.status(ok ? 200 : 400)
|
|
122
|
+
.type("text/html")
|
|
123
|
+
.send(html ?? renderFallbackCallbackHtml(ok ? "OAuth complete" : "OAuth failed", message || "(no message)"));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
router.get(API_ROUTES.plugins.runtimeOauthCallback, async (req: Request<{ alias: string }>, res: Response) => {
|
|
127
|
+
const { alias } = req.params;
|
|
128
|
+
const plugin = getRuntimePluginByOauthAlias(alias);
|
|
129
|
+
if (!plugin) {
|
|
130
|
+
notFound(res, `no runtime plugin registered for OAuth callback alias "${alias}"`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!plugin.execute) {
|
|
134
|
+
serverError(res, `runtime plugin "${plugin.name}" has no execute()`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const result = await plugin.execute({}, buildOauthCallbackArgs(req.query));
|
|
139
|
+
sendOauthCallbackResult(res, result);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
log.error(LOG_PREFIX, "oauth callback dispatch threw", { alias, plugin: plugin.name, error: errorMessage(err) });
|
|
142
|
+
res
|
|
143
|
+
.status(500)
|
|
144
|
+
.type("text/html")
|
|
145
|
+
.send(renderFallbackCallbackHtml("Plugin error", errorMessage(err)));
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function renderFallbackCallbackHtml(title: string, body: string): string {
|
|
150
|
+
// Minimal fallback when the plugin doesn't return its own HTML.
|
|
151
|
+
// Plugins are encouraged to return a richer page; this is just a
|
|
152
|
+
// safety net so the browser always gets something readable.
|
|
153
|
+
const escape = (value: string) =>
|
|
154
|
+
value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
155
|
+
return `<!doctype html><html lang="en"><meta charset="utf-8"><title>${escape(title)}</title>
|
|
156
|
+
<style>body{font-family:system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem;color:#111}h1{margin-bottom:1rem}pre{white-space:pre-wrap;background:#f5f5f5;padding:1rem;border-radius:.5rem}</style>
|
|
157
|
+
<h1>${escape(title)}</h1>
|
|
158
|
+
<pre>${escape(body)}</pre>
|
|
159
|
+
</html>`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Static-mount of an installed plugin's directory. Resolution flow:
|
|
163
|
+
//
|
|
164
|
+
// 1. Look up `(pkg, version)` in the runtime registry. Presets and
|
|
165
|
+
// user-installed plugins are both registered server-side, with
|
|
166
|
+
// cachePath set from a trusted source (preset list or workspace
|
|
167
|
+
// ledger). If the URL doesn't match any registered entry, 404 —
|
|
168
|
+
// this is the trust boundary that prevents arbitrary-file reads
|
|
169
|
+
// via percent-encoded `../` in `pkg` / `version` (the bearer-
|
|
170
|
+
// auth exemption makes this an unauthenticated path).
|
|
171
|
+
// 2. realpath the registered cachePath. Symlinks inside the
|
|
172
|
+
// extracted tree (e.g. dist/foo.js → /etc/passwd) cannot escape
|
|
173
|
+
// because `resolveWithinRoot(rootReal, subPath)` rejects any
|
|
174
|
+
// target that resolves outside the plugin's own root.
|
|
175
|
+
//
|
|
176
|
+
// The earlier "must be inside WORKSPACE_PATHS.pluginCache" anchor is
|
|
177
|
+
// gone — presets live under `node_modules/<pkg>/`, not in the
|
|
178
|
+
// workspace cache. The registry-membership check replaces that
|
|
179
|
+
// anchor: the registry is server-set, so its cachePath values are
|
|
180
|
+
// already trusted regardless of where on disk they point.
|
|
181
|
+
/** Look up a registered plugin and return the realpath of its root.
|
|
182
|
+
* Returns null when the (pkg, version) pair is not registered, when
|
|
183
|
+
* the cachePath does not exist on disk, or when realpath fails.
|
|
184
|
+
* Exported for tests. */
|
|
185
|
+
export function resolvePluginRoot(pkg: string, version: string): string | null {
|
|
186
|
+
const plugin = getRuntimePlugins().find((entry) => entry.name === pkg && entry.version === version);
|
|
187
|
+
if (!plugin) return null;
|
|
188
|
+
try {
|
|
189
|
+
return realpathSync(plugin.cachePath);
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
router.get(API_ROUTES.plugins.runtimeAsset, async (req: Request<{ pkg: string; version: string; splat?: string | string[] }>, res: Response) => {
|
|
196
|
+
const pkg = decodeURIComponent(req.params.pkg);
|
|
197
|
+
const version = decodeURIComponent(req.params.version);
|
|
198
|
+
// Express 5 returns `splat` as `string[]` when the wildcard
|
|
199
|
+
// matched multiple segments, `string` for a single segment, and
|
|
200
|
+
// empty/undefined for an empty wildcard. Normalise to the joined
|
|
201
|
+
// path so downstream `path.join` works on every shape.
|
|
202
|
+
const rawSplat = req.params.splat;
|
|
203
|
+
const subPath = Array.isArray(rawSplat) ? rawSplat.join("/") : (rawSplat ?? "");
|
|
204
|
+
const rootReal = resolvePluginRoot(pkg, version);
|
|
205
|
+
if (!rootReal) {
|
|
206
|
+
notFound(res, "asset not found");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const resolved = resolveWithinRoot(rootReal, subPath);
|
|
210
|
+
if (!resolved) {
|
|
211
|
+
notFound(res, "asset not found");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const { data, contentType } = await readPluginAsset(resolved);
|
|
216
|
+
res.setHeader("Content-Type", contentType);
|
|
217
|
+
res.send(data);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
log.error(LOG_PREFIX, "asset read failed", { pkg, version, subPath, error: errorMessage(err) });
|
|
220
|
+
serverError(res, "asset read failed");
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
export default router;
|
|
@@ -3,12 +3,13 @@ import { loadSchedulerItems, saveSchedulerItems } from "../../utils/files/schedu
|
|
|
3
3
|
import { dispatchScheduler, type SchedulerActionInput } from "./schedulerHandlers.js";
|
|
4
4
|
import { respondWithDispatchResult, type DispatchSuccessResponse, type DispatchErrorResponse } from "./dispatchResponse.js";
|
|
5
5
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
6
|
+
import { bindRoute } from "../../utils/router.js";
|
|
6
7
|
import { SESSION_ORIGINS } from "../../../src/types/session.js";
|
|
7
8
|
import { loadUserTasks, validateAndCreate, refreshUserTasks } from "../../workspace/skills/user-tasks.js";
|
|
8
9
|
import { saveUserTasks } from "../../utils/files/user-tasks-io.js";
|
|
9
10
|
import { startChat } from "./agent.js";
|
|
10
11
|
import { log } from "../../system/logger/index.js";
|
|
11
|
-
import { SCHEDULER_ACTIONS, TASK_ACTIONS } from "../../../src/
|
|
12
|
+
import { SCHEDULER_ACTIONS, TASK_ACTIONS } from "../../../src/plugins/scheduler/actions.js";
|
|
12
13
|
import { badRequest, notFound, serverError } from "../../utils/httpError.js";
|
|
13
14
|
import { errorMessage } from "../../utils/errors.js";
|
|
14
15
|
import { makeUuid } from "../../utils/id.js";
|
|
@@ -30,7 +31,7 @@ function saveItems(items: ScheduledItem[]): void {
|
|
|
30
31
|
saveSchedulerItems(items);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
router
|
|
34
|
+
bindRoute(router, API_ROUTES.scheduler.list, (_req: Request, res: Response<{ data: { items: ScheduledItem[] } }>) => {
|
|
34
35
|
res.json({ data: { items: loadItems() } });
|
|
35
36
|
});
|
|
36
37
|
|
|
@@ -43,8 +44,9 @@ interface SchedulerBody extends SchedulerActionInput {
|
|
|
43
44
|
roleId?: string;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
bindRoute(
|
|
48
|
+
router,
|
|
49
|
+
API_ROUTES.scheduler.dispatch,
|
|
48
50
|
async (req: Request<object, unknown, SchedulerBody>, res: Response<DispatchSuccessResponse<ScheduledItem> | DispatchErrorResponse | unknown>) => {
|
|
49
51
|
const { action, ...input } = req.body;
|
|
50
52
|
|
|
@@ -108,7 +110,7 @@ async function handleTaskAction(action: string, input: Record<string, unknown>,
|
|
|
108
110
|
notFound(res, `task not found: ${taskId}`);
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
|
-
const name = tasks[idx]
|
|
113
|
+
const { name } = tasks[idx];
|
|
112
114
|
tasks.splice(idx, 1);
|
|
113
115
|
await saveUserTasks(tasks);
|
|
114
116
|
await refreshUserTasks();
|
|
@@ -86,7 +86,7 @@ function applyPropPatch(current: ScheduledItem["props"], patch: Record<string, s
|
|
|
86
86
|
const next: ScheduledItem["props"] = { ...current };
|
|
87
87
|
for (const [key, value] of Object.entries(patch)) {
|
|
88
88
|
if (value === null) {
|
|
89
|
-
|
|
89
|
+
Reflect.deleteProperty(next, key);
|
|
90
90
|
} else {
|
|
91
91
|
next[key] = value;
|
|
92
92
|
}
|
|
@@ -11,6 +11,7 @@ import { Router, type Request, type Response } from "express";
|
|
|
11
11
|
import { getSchedulerTasks, getSchedulerLogs } from "../../events/scheduler-adapter.js";
|
|
12
12
|
import type { TaskLogEntry } from "@receptron/task-scheduler";
|
|
13
13
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
14
|
+
import { bindRoute } from "../../utils/router.js";
|
|
14
15
|
import { SESSION_ORIGINS } from "../../../src/types/session.js";
|
|
15
16
|
import { loadUserTasks, validateAndCreate, applyUpdate, withUserTaskLock } from "../../workspace/skills/user-tasks.js";
|
|
16
17
|
import { badRequest, notFound, serverError } from "../../utils/httpError.js";
|
|
@@ -24,7 +25,7 @@ const router = Router();
|
|
|
24
25
|
|
|
25
26
|
// ── List all tasks ──────────────────────────────────────────────
|
|
26
27
|
|
|
27
|
-
router
|
|
28
|
+
bindRoute(router, API_ROUTES.scheduler.tasksList, (_req: Request, res: Response) => {
|
|
28
29
|
log.info("scheduler-tasks", "list: start");
|
|
29
30
|
try {
|
|
30
31
|
// getSchedulerTasks() returns system-only tasks (registered via
|
|
@@ -49,7 +50,7 @@ router.get(API_ROUTES.scheduler.tasks, (_req: Request, res: Response) => {
|
|
|
49
50
|
|
|
50
51
|
// ── Create user task ────────────────────────────────────────────
|
|
51
52
|
|
|
52
|
-
router
|
|
53
|
+
bindRoute(router, API_ROUTES.scheduler.tasksCreate, async (req: Request, res: Response) => {
|
|
53
54
|
log.info("scheduler-tasks", "create: start");
|
|
54
55
|
const validated = validateAndCreate(req.body);
|
|
55
56
|
if (validated.kind === "error") {
|
|
@@ -74,7 +75,7 @@ router.post(API_ROUTES.scheduler.tasks, async (req: Request, res: Response) => {
|
|
|
74
75
|
|
|
75
76
|
// ── Update user task ────────────────────────────────────────────
|
|
76
77
|
|
|
77
|
-
router
|
|
78
|
+
bindRoute(router, API_ROUTES.scheduler.taskUpdate, async (req: Request<{ id: string }>, res: Response) => {
|
|
78
79
|
const { id: taskId } = req.params;
|
|
79
80
|
log.info("scheduler-tasks", "update: start", { taskId });
|
|
80
81
|
try {
|
|
@@ -102,7 +103,7 @@ router.put(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, res:
|
|
|
102
103
|
|
|
103
104
|
// ── Delete user task ────────────────────────────────────────────
|
|
104
105
|
|
|
105
|
-
router
|
|
106
|
+
bindRoute(router, API_ROUTES.scheduler.taskDelete, async (req: Request<{ id: string }>, res: Response) => {
|
|
106
107
|
const { id: taskId } = req.params;
|
|
107
108
|
log.info("scheduler-tasks", "delete: start", { taskId });
|
|
108
109
|
try {
|
|
@@ -128,7 +129,7 @@ router.delete(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, re
|
|
|
128
129
|
|
|
129
130
|
// ── Manual trigger ──────────────────────────────────────────────
|
|
130
131
|
|
|
131
|
-
router
|
|
132
|
+
bindRoute(router, API_ROUTES.scheduler.taskRun, async (req: Request<{ id: string }>, res: Response) => {
|
|
132
133
|
const { id: taskId } = req.params;
|
|
133
134
|
log.info("scheduler-tasks", "run: start", { taskId });
|
|
134
135
|
// Check user tasks first
|
|
@@ -176,11 +177,11 @@ interface LogQuery {
|
|
|
176
177
|
limit?: string;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
router
|
|
180
|
+
bindRoute(router, API_ROUTES.scheduler.logs, async (req: Request<object, unknown, object, LogQuery>, res: Response<{ logs: TaskLogEntry[] }>) => {
|
|
180
181
|
const MAX_LIMIT = 500;
|
|
181
182
|
const rawLimitStr = getOptionalStringQuery(req, "limit");
|
|
182
183
|
const rawLimit = rawLimitStr ? parseInt(rawLimitStr, 10) : undefined;
|
|
183
|
-
const limit = Number.isFinite(rawLimit) && rawLimit
|
|
184
|
+
const limit = rawLimit !== undefined && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, MAX_LIMIT) : undefined;
|
|
184
185
|
const taskId = getOptionalStringQuery(req, "taskId");
|
|
185
186
|
log.info("scheduler-tasks", "logs: start", { taskId, limit });
|
|
186
187
|
try {
|