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
|
@@ -2,7 +2,7 @@ import { Router, Request, Response } from "express";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { WORKSPACE_PATHS } from "../../workspace/paths.js";
|
|
4
4
|
import { readTextSafeSync, readTextSafe } from "../../utils/files/safe.js";
|
|
5
|
-
import {
|
|
5
|
+
import { writeWikiPage } from "../../workspace/wiki-pages/io.js";
|
|
6
6
|
import { getPageIndex } from "./wiki/pageIndex.js";
|
|
7
7
|
import { parseFrontmatterTags } from "./wiki/frontmatter.js";
|
|
8
8
|
import { badRequest, notFound } from "../../utils/httpError.js";
|
|
@@ -15,6 +15,7 @@ import { previewSnippet } from "../../utils/logPreview.js";
|
|
|
15
15
|
// different name avoids the no-shadow clash without renaming the
|
|
16
16
|
// long-standing local.
|
|
17
17
|
import { errorMessage as formatError } from "../../utils/errors.js";
|
|
18
|
+
import { BULLET_LINK_PATTERN, BULLET_WIKI_LINK_PATTERN } from "../../utils/regex.js";
|
|
18
19
|
|
|
19
20
|
const router = Router();
|
|
20
21
|
|
|
@@ -44,12 +45,12 @@ export function wikiSlugify(text: string): string {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
const TABLE_SEPARATOR_PATTERN = /^\|[\s|:-]+\|$/;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
48
|
+
// Bullet-link patterns (BULLET_LINK_PATTERN, BULLET_WIKI_LINK_PATTERN)
|
|
49
|
+
// live in `server/utils/regex.ts` alongside other server regex audit
|
|
50
|
+
// notes. Capture the href (group 2) alongside the title (group 1) so
|
|
51
|
+
// we can derive the slug from the file name instead of re-slugifying
|
|
52
|
+
// the title — important for non-ASCII titles like "さくらインターネット"
|
|
50
53
|
// where `wikiSlugify` returns "" and the slug would otherwise be lost.
|
|
51
|
-
const BULLET_LINK_PATTERN = /^[-*]\s+\[([^\]]+)\]\(([^)]*)\)(?:\s*[—–-]\s*(.*))?/;
|
|
52
|
-
const BULLET_WIKI_LINK_PATTERN = /^[-*]\s+\[\[([^\]]+)\]\](?:\s*[—–-]\s*(.*))?/;
|
|
53
54
|
// Unicode-aware tag body: any letter or number in any script
|
|
54
55
|
// (so Japanese / Chinese / Korean tags like `#クラウド` or `#可視化`
|
|
55
56
|
// work), plus `-` and `_` as internal joiners. First char is a
|
|
@@ -103,6 +104,28 @@ export function buildTableColumnMap(headerRow: string): Map<string, number> {
|
|
|
103
104
|
return map;
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
interface TableColumnIndices {
|
|
108
|
+
slug: number;
|
|
109
|
+
title: number;
|
|
110
|
+
summary: number;
|
|
111
|
+
/** Undefined when the table has no `tags` column — caller skips
|
|
112
|
+
* the tags lookup entirely and the row gets `tags: []`. */
|
|
113
|
+
tags: number | undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Resolve the per-column indices the row parser needs. Falls back
|
|
117
|
+
* to positional defaults (0/1/2) when the table has no header map.
|
|
118
|
+
* "summary" is the canonical column name; "description" is accepted
|
|
119
|
+
* as a legacy alias used by older fixtures. */
|
|
120
|
+
function resolveTableColumnIndices(columnMap: Map<string, number> | null): TableColumnIndices {
|
|
121
|
+
return {
|
|
122
|
+
slug: columnMap?.get("slug") ?? 0,
|
|
123
|
+
title: columnMap?.get("title") ?? 1,
|
|
124
|
+
summary: columnMap?.get("summary") ?? columnMap?.get("description") ?? 2,
|
|
125
|
+
tags: columnMap?.get("tags"),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
// Each parser returns the entry it produced (if any). The parent
|
|
107
130
|
// loop tries them in order; the first non-null result wins.
|
|
108
131
|
function parseTableRow(trimmed: string, columnMap: Map<string, number> | null): WikiPageEntry | null {
|
|
@@ -111,19 +134,14 @@ function parseTableRow(trimmed: string, columnMap: Map<string, number> | null):
|
|
|
111
134
|
.slice(1, -1)
|
|
112
135
|
.map((column) => column.trim().replace(/^`|`$/g, ""));
|
|
113
136
|
if (cols.length < 2) return null;
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// existing unit test fixture). Fall back to column 2 when the
|
|
119
|
-
// table has no header map.
|
|
120
|
-
const summaryIdx = columnMap?.get("summary") ?? columnMap?.get("description") ?? 2;
|
|
121
|
-
const tagsIdx = columnMap?.get("tags");
|
|
122
|
-
const slug = cols[slugIdx] ?? "";
|
|
123
|
-
const title = cols[titleIdx] || slug;
|
|
124
|
-
const description = cols[summaryIdx] ?? "";
|
|
125
|
-
const tags = tagsIdx !== undefined ? parseTagsCell(cols[tagsIdx] ?? "") : [];
|
|
137
|
+
|
|
138
|
+
const idx = resolveTableColumnIndices(columnMap);
|
|
139
|
+
const slug = cols[idx.slug] ?? "";
|
|
140
|
+
const title = cols[idx.title] || slug;
|
|
126
141
|
if (!slug || !title) return null;
|
|
142
|
+
|
|
143
|
+
const description = cols[idx.summary] ?? "";
|
|
144
|
+
const tags = idx.tags !== undefined ? parseTagsCell(cols[idx.tags] ?? "") : [];
|
|
127
145
|
return { title, slug, description, tags };
|
|
128
146
|
}
|
|
129
147
|
|
|
@@ -233,8 +251,9 @@ async function resolvePagePath(pageName: string): Promise<string | null> {
|
|
|
233
251
|
const indexContent = readFileOrEmpty(indexFile());
|
|
234
252
|
const entries = parseIndexEntries(indexContent);
|
|
235
253
|
const titleMatch = entries.find((entry) => entry.title === pageName);
|
|
236
|
-
if (titleMatch
|
|
237
|
-
|
|
254
|
+
if (titleMatch) {
|
|
255
|
+
const file = slugs.get(titleMatch.slug);
|
|
256
|
+
if (file) return path.join(dir, file);
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
return null;
|
|
@@ -320,7 +339,7 @@ function buildIndexResponse(action: string): WikiResponse {
|
|
|
320
339
|
// instructions distinctions that the GET and POST handlers share.
|
|
321
340
|
export function buildPageResponseData(args: { action: string; pageName: string; resolvedTitle: string; content: string; exists: boolean }): WikiResponse {
|
|
322
341
|
const { action, pageName, resolvedTitle, content, exists } = args;
|
|
323
|
-
const hasContent =
|
|
342
|
+
const hasContent = Boolean(content);
|
|
324
343
|
// Three states:
|
|
325
344
|
// 1. !exists → page file is missing entirely.
|
|
326
345
|
// 2. exists && !hasContent → page file exists but is empty (e.g.,
|
|
@@ -370,7 +389,7 @@ export function toPageResponse(args: { action: string; pageName: string; filePat
|
|
|
370
389
|
pageName,
|
|
371
390
|
resolvedTitle,
|
|
372
391
|
content,
|
|
373
|
-
exists:
|
|
392
|
+
exists: Boolean(filePath),
|
|
374
393
|
});
|
|
375
394
|
}
|
|
376
395
|
|
|
@@ -508,9 +527,14 @@ type SaveOutcome = { ok: true; absPath: string } | { ok: false; reason: "not-fou
|
|
|
508
527
|
async function saveExistingPage(pageName: string, content: string): Promise<SaveOutcome> {
|
|
509
528
|
const absPath = await resolvePagePath(pageName);
|
|
510
529
|
if (!absPath) return { ok: false, reason: "not-found" };
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
|
|
530
|
+
// Funnel through the wiki-page write helper. Atomic write is
|
|
531
|
+
// guaranteed inside; the helper also routes the (old, new) pair
|
|
532
|
+
// to the snapshot pipeline (#763 PR 2 — currently a no-op stub).
|
|
533
|
+
// Editor identity defaults to "user" here because the route is
|
|
534
|
+
// hit by both LLM (`manageWiki` MCP) and frontend saves; PR 2
|
|
535
|
+
// disambiguates them via a request-side flag.
|
|
536
|
+
const slug = path.basename(absPath, ".md");
|
|
537
|
+
await writeWikiPage(slug, content, { editor: "user" });
|
|
514
538
|
return { ok: true, absPath };
|
|
515
539
|
}
|
|
516
540
|
|
|
@@ -529,7 +553,7 @@ async function handleSaveAction(
|
|
|
529
553
|
badRequest(res, "pageName required for save action");
|
|
530
554
|
return;
|
|
531
555
|
}
|
|
532
|
-
const content = req.body
|
|
556
|
+
const { content } = req.body;
|
|
533
557
|
if (typeof content !== "string") {
|
|
534
558
|
log.warn("wiki", "POST save: missing content", { pageNamePreview: previewSnippet(pageName) });
|
|
535
559
|
badRequest(res, "content (string) required for save action");
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// File-change publisher. Single place that knows how to broadcast
|
|
2
|
+
// "this workspace file changed" — every route that writes through the
|
|
3
|
+
// app should call `publishFileChange(relPath)` after a successful
|
|
4
|
+
// write so that subscribed UI tabs (and other browsers) refetch.
|
|
5
|
+
//
|
|
6
|
+
// Module-singleton + `init…` pattern, mirroring `session-store`. The
|
|
7
|
+
// singleton is null at import time so unit-tested route handlers can
|
|
8
|
+
// skip wiring and simply observe a no-op publish.
|
|
9
|
+
//
|
|
10
|
+
// Channel name + payload shape live in `src/config/pubsubChannels.ts`
|
|
11
|
+
// so subscribers can't drift from the publisher.
|
|
12
|
+
|
|
13
|
+
import { stat } from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import type { IPubSub } from "./pub-sub/index.js";
|
|
16
|
+
import { fileChannel, toPosixWorkspacePath, type FileChannelPayload } from "../../src/config/pubsubChannels.js";
|
|
17
|
+
import { workspacePath } from "../workspace/workspace.js";
|
|
18
|
+
import { maybeRegenerateTopicIndex, TOPIC_INDEX_RELATIVE_PATH } from "../workspace/memory/topic-index-hook.js";
|
|
19
|
+
import { log } from "../system/logger/index.js";
|
|
20
|
+
|
|
21
|
+
let pubsub: IPubSub | null = null;
|
|
22
|
+
|
|
23
|
+
export function initFileChangePublisher(instance: IPubSub): void {
|
|
24
|
+
pubsub = instance;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Publish a file-change event for a workspace-relative path. Reads
|
|
29
|
+
* the post-write `mtimeMs` via `stat` so subscribers get a monotonic
|
|
30
|
+
* timestamp suitable for both cache-busting (`?v=<mtime>`) and
|
|
31
|
+
* out-of-order drops. If the stat fails (file just got deleted, race
|
|
32
|
+
* with another writer, etc.) we fall back to `Date.now()` rather than
|
|
33
|
+
* dropping the event — losing the notification is worse than an
|
|
34
|
+
* approximate timestamp.
|
|
35
|
+
*/
|
|
36
|
+
export async function publishFileChange(relativePath: string): Promise<void> {
|
|
37
|
+
if (!pubsub) return;
|
|
38
|
+
const absPath = path.join(workspacePath, relativePath);
|
|
39
|
+
let mtimeMs: number;
|
|
40
|
+
try {
|
|
41
|
+
({ mtimeMs } = await stat(absPath));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log.warn("file-change", "stat failed; falling back to Date.now()", {
|
|
44
|
+
pathPreview: relativePath,
|
|
45
|
+
error: err instanceof Error ? err.message : String(err),
|
|
46
|
+
});
|
|
47
|
+
mtimeMs = Date.now();
|
|
48
|
+
}
|
|
49
|
+
// Normalise once so `payload.path` and the channel suffix can't drift
|
|
50
|
+
// on Windows / mixed-separator inputs.
|
|
51
|
+
const posixPath = toPosixWorkspacePath(relativePath);
|
|
52
|
+
const payload: FileChannelPayload = { path: posixPath, mtimeMs };
|
|
53
|
+
// Callers fire-and-forget via `void publishFileChange(...)`, so a
|
|
54
|
+
// throw here would surface as an unhandled rejection (Node terminates
|
|
55
|
+
// the process by default). Missing one notification is strictly less
|
|
56
|
+
// bad than killing the server.
|
|
57
|
+
try {
|
|
58
|
+
pubsub.publish(fileChannel(posixPath), payload);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log.warn("file-change", "publish failed; subscribers will miss this event", {
|
|
61
|
+
pathPreview: posixPath,
|
|
62
|
+
error: err instanceof Error ? err.message : String(err),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Side-effect hook: keep the topic-format MEMORY.md index in sync
|
|
66
|
+
// when a user edits a topic file via the file explorer (#1032).
|
|
67
|
+
// No-op for non-topic paths. Fire-and-forget so the publish path
|
|
68
|
+
// stays fast; errors log internally.
|
|
69
|
+
//
|
|
70
|
+
// When regen actually runs, also emit a change event for the
|
|
71
|
+
// index file itself so a FilesView tab pinned to MEMORY.md
|
|
72
|
+
// refreshes the moment the rebuild lands. The recursive call is
|
|
73
|
+
// bounded: `MEMORY.md` is excluded by `isTopicFilePath`, so the
|
|
74
|
+
// second pass returns false without triggering another regen.
|
|
75
|
+
maybeRegenerateTopicIndex(posixPath)
|
|
76
|
+
.then((didRegen) => (didRegen ? publishFileChange(TOPIC_INDEX_RELATIVE_PATH) : undefined))
|
|
77
|
+
.catch(() => {});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Test-only — clear the module singleton so each test starts clean. */
|
|
81
|
+
export function _resetFileChangePublisherForTesting(): void {
|
|
82
|
+
pubsub = null;
|
|
83
|
+
}
|
|
@@ -1,128 +1,285 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Legacy host-side `publishNotification()` entry point — now a thin
|
|
2
|
+
// wrapper over the new notifier engine (PR 4 of feat-encore).
|
|
2
3
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// The signature is preserved so the existing host call sites
|
|
5
|
+
// (`server/agent/mcp-tools/notify.ts`, `server/api/routes/notifications.ts`,
|
|
6
|
+
// `server/workspace/sources/pipeline/notify.ts`,
|
|
7
|
+
// `server/plugins/diagnostics.ts`) keep working without source changes.
|
|
8
|
+
// Internally it now:
|
|
9
|
+
// 1. Maps the legacy `NotificationKind` source category to a
|
|
10
|
+
// `pluginPkg` ("todo"/"scheduler"/"agent"/"journal" keep their
|
|
11
|
+
// names; "push"/"bridge"/"system" collapse under "host").
|
|
12
|
+
// 2. Maps `priority` to engine `severity` ("normal" → "nudge",
|
|
13
|
+
// "high" → "urgent").
|
|
14
|
+
// 3. Flattens the typed `NotificationAction` to a relative URL
|
|
15
|
+
// string for `navigateTarget` (forwarded only as metadata —
|
|
16
|
+
// legacy entries publish with `lifecycle: "fyi"`, so the
|
|
17
|
+
// `action`-lifecycle rules don't apply and clicking still routes).
|
|
18
|
+
// 4. Stashes the legacy fields (`kind`, `priority`, `action`,
|
|
19
|
+
// `i18n`, `transportId`, `sessionId`, the caller-supplied dedup
|
|
20
|
+
// `id`) on `pluginData` so the bell can preserve icon, i18n
|
|
21
|
+
// localization, and transport routing.
|
|
6
22
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
23
|
+
// Bridge push (`chat-service.pushToBridge`) and macOS Reminder push
|
|
24
|
+
// no longer happen inline here — they're owned by adapters that
|
|
25
|
+
// subscribe to the `notifier` pubsub channel. See
|
|
26
|
+
// `server/notifier/legacy-adapters.ts`.
|
|
11
27
|
|
|
12
|
-
import {
|
|
28
|
+
import { PAGE_ROUTES } from "../../src/router/pageRoutes.js";
|
|
13
29
|
import {
|
|
14
|
-
NOTIFICATION_KINDS,
|
|
15
|
-
NOTIFICATION_ICONS,
|
|
16
30
|
NOTIFICATION_ACTION_TYPES,
|
|
31
|
+
NOTIFICATION_KINDS,
|
|
17
32
|
NOTIFICATION_PRIORITIES,
|
|
18
|
-
|
|
19
|
-
type NotificationKind,
|
|
33
|
+
NOTIFICATION_VIEWS,
|
|
20
34
|
type NotificationAction,
|
|
35
|
+
type NotificationI18n,
|
|
36
|
+
type NotificationKind,
|
|
21
37
|
type NotificationPriority,
|
|
22
38
|
} from "../../src/types/notification.js";
|
|
39
|
+
import { publish as notifierPublish } from "../notifier/engine.js";
|
|
40
|
+
import type { NotifierSeverity } from "../notifier/types.js";
|
|
23
41
|
import { ONE_SECOND_MS, MAX_NOTIFICATION_DELAY_SEC } from "../utils/time.js";
|
|
24
42
|
import { log } from "../system/logger/index.js";
|
|
25
43
|
import { makeUuid } from "../utils/id.js";
|
|
26
|
-
import { pushToMacosReminder } from "../system/macosNotify.js";
|
|
27
44
|
|
|
28
|
-
// ──
|
|
45
|
+
// ── Public types ────────────────────────────────────────────────
|
|
29
46
|
|
|
30
|
-
export interface
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
export interface PublishNotificationOpts {
|
|
48
|
+
kind: NotificationKind;
|
|
49
|
+
title: string;
|
|
50
|
+
body?: string;
|
|
51
|
+
action?: NotificationAction;
|
|
52
|
+
priority?: NotificationPriority;
|
|
53
|
+
sessionId?: string;
|
|
54
|
+
transportId?: string;
|
|
55
|
+
/** Override the auto-generated UUID with a caller-supplied stable
|
|
56
|
+
* id. Used by the plugin-meta diagnostics: the same diagnostic
|
|
57
|
+
* id is returned from `/api/plugins/diagnostics`, and `pluginData`
|
|
58
|
+
* carries it so `announcePluginMetaDiagnostics` can dedupe across
|
|
59
|
+
* reboots without piling identical entries into `active.json`. */
|
|
60
|
+
id?: string;
|
|
61
|
+
/** vue-i18n keys + params for clients to localize the title/body.
|
|
62
|
+
* Server-side `title` / `body` stay set as English fallbacks for
|
|
63
|
+
* logs, macOS Reminder push, and bridge push paths. */
|
|
64
|
+
i18n?: NotificationI18n;
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
|
|
67
|
+
/** Discriminated marker on `NotifierEntry.pluginData` for entries
|
|
68
|
+
* produced by the legacy `publishNotification()` wrapper. The bell
|
|
69
|
+
* reads this to preserve the legacy icon, i18n localization, and
|
|
70
|
+
* transport routing. New direct callers of `notifier.publish()`
|
|
71
|
+
* publish without this shape. */
|
|
72
|
+
export interface LegacyNotifierPluginData {
|
|
73
|
+
legacy: true;
|
|
74
|
+
/** Caller-supplied stable id (e.g. plugin-meta diagnostic id), or
|
|
75
|
+
* the auto-generated UUID otherwise. Distinct from
|
|
76
|
+
* `NotifierEntry.id`, which the engine assigns. */
|
|
77
|
+
legacyId: string;
|
|
78
|
+
kind: NotificationKind;
|
|
79
|
+
priority: NotificationPriority;
|
|
80
|
+
action: NotificationAction;
|
|
81
|
+
i18n?: NotificationI18n;
|
|
82
|
+
transportId?: string;
|
|
83
|
+
sessionId?: string;
|
|
84
|
+
}
|
|
36
85
|
|
|
37
|
-
export function
|
|
38
|
-
|
|
86
|
+
export function isLegacyNotifierPluginData(value: unknown): value is LegacyNotifierPluginData {
|
|
87
|
+
if (value === null || typeof value !== "object") return false;
|
|
88
|
+
const rec = value as Record<string, unknown>;
|
|
89
|
+
return rec.legacy === true && typeof rec.legacyId === "string" && typeof rec.kind === "string";
|
|
39
90
|
}
|
|
40
91
|
|
|
41
|
-
// ──
|
|
92
|
+
// ── Mapping helpers ─────────────────────────────────────────────
|
|
42
93
|
|
|
43
|
-
|
|
44
|
-
|
|
94
|
+
/** Map legacy `NotificationKind` → `pluginPkg`. Source categories
|
|
95
|
+
* that come from a domain feature (todo / scheduler / agent /
|
|
96
|
+
* journal) keep their names so a future runtime plugin can adopt the
|
|
97
|
+
* pluginPkg unchanged; the host-internal categories (push, bridge,
|
|
98
|
+
* system) collapse to "host". */
|
|
99
|
+
export function legacyKindToPluginPkg(kind: NotificationKind): string {
|
|
100
|
+
switch (kind) {
|
|
101
|
+
case NOTIFICATION_KINDS.todo:
|
|
102
|
+
case NOTIFICATION_KINDS.scheduler:
|
|
103
|
+
case NOTIFICATION_KINDS.agent:
|
|
104
|
+
case NOTIFICATION_KINDS.journal:
|
|
105
|
+
return kind;
|
|
106
|
+
case NOTIFICATION_KINDS.push:
|
|
107
|
+
case NOTIFICATION_KINDS.bridge:
|
|
108
|
+
case NOTIFICATION_KINDS.system:
|
|
109
|
+
default:
|
|
110
|
+
return "host";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
45
113
|
|
|
46
|
-
|
|
47
|
-
|
|
114
|
+
/** Map legacy `priority` → engine `severity`. The legacy badge was
|
|
115
|
+
* red unconditionally; mapping `normal` → `nudge` keeps the bell
|
|
116
|
+
* visibly amber so users still see "you have notifications," while
|
|
117
|
+
* `high` → `urgent` preserves the red escalation for diagnostics
|
|
118
|
+
* and similar attention-grabbing items. `info` is reserved for
|
|
119
|
+
* future non-bell-coloring callers. */
|
|
120
|
+
export function legacyPriorityToSeverity(priority: NotificationPriority | undefined): NotifierSeverity {
|
|
121
|
+
return priority === NOTIFICATION_PRIORITIES.high ? "urgent" : "nudge";
|
|
48
122
|
}
|
|
49
123
|
|
|
50
|
-
// ──
|
|
124
|
+
// ── Per-view URL builders ───────────────────────────────────────
|
|
125
|
+
//
|
|
126
|
+
// Split out from `legacyActionToNavigateTarget` so the dispatcher
|
|
127
|
+
// stays under the cognitive-complexity threshold. Each builder takes
|
|
128
|
+
// the typed target slice it cares about and returns either a relative
|
|
129
|
+
// URL or `undefined` (the latter only when a required field is
|
|
130
|
+
// missing — chat without sessionId).
|
|
51
131
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
132
|
+
type NavigateTarget = Extract<NotificationAction, { type: "navigate" }>["target"];
|
|
133
|
+
|
|
134
|
+
// User/content-derived path segments (slugs, anchors, item ids) ride
|
|
135
|
+
// through `encodeURIComponent` so reserved characters like `?`, `#`,
|
|
136
|
+
// `/`, `%`, `&`, ` ` don't change the URL's structure when interpolated.
|
|
137
|
+
// `PAGE_ROUTES.*` and the literal `pages` segment are static literals
|
|
138
|
+
// — encoding them is unnecessary noise.
|
|
139
|
+
//
|
|
140
|
+
// Dot segments (`.` and `..`) survive `encodeURIComponent` (the dots
|
|
141
|
+
// aren't reserved characters) and would let a slug or path component
|
|
142
|
+
// jump out of its view's namespace once the browser / router applies
|
|
143
|
+
// path normalization. `/files/../chat/sess` collapses to `/chat/sess`,
|
|
144
|
+
// for example. `isSafePathComponent` rejects those segments; consumers
|
|
145
|
+
// either fall back to the view's index (single-component fields like
|
|
146
|
+
// `slug` / `itemId`) or drop the navigate target entirely (chat,
|
|
147
|
+
// where `sessionId` is required, has no usable index).
|
|
148
|
+
|
|
149
|
+
function isSafePathComponent(segment: string): boolean {
|
|
150
|
+
return segment !== "." && segment !== "..";
|
|
60
151
|
}
|
|
61
152
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
153
|
+
function buildChatTarget(target: Extract<NavigateTarget, { view: typeof NOTIFICATION_VIEWS.chat }>): string | undefined {
|
|
154
|
+
// No sessionId → drop the action; bouncing off the catch-all
|
|
155
|
+
// redirect is worse UX than a non-clickable entry. Dot-segment
|
|
156
|
+
// sessionId would normalize off /chat, so drop too.
|
|
157
|
+
if (!target.sessionId || !isSafePathComponent(target.sessionId)) return undefined;
|
|
158
|
+
const sessionPath = `/${PAGE_ROUTES.chat}/${encodeURIComponent(target.sessionId)}`;
|
|
159
|
+
if (target.resultUuid) return `${sessionPath}?result=${encodeURIComponent(target.resultUuid)}`;
|
|
160
|
+
return sessionPath;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildFilesTarget(target: Extract<NavigateTarget, { view: typeof NOTIFICATION_VIEWS.files }>): string {
|
|
164
|
+
// Files uses a catch-all (`/files/:pathMatch(.*)`); empty path
|
|
165
|
+
// lands on the index. Each segment is encoded so spaces / special
|
|
166
|
+
// characters don't break the URL. A path containing `.` or `..`
|
|
167
|
+
// segments would normalize out of /files, so refuse to build a
|
|
168
|
+
// path target for it — the index is the safest fallback.
|
|
169
|
+
if (!target.path) return `/${PAGE_ROUTES.files}`;
|
|
170
|
+
const segments = target.path.split("/").filter(Boolean);
|
|
171
|
+
if (segments.some((segment) => !isSafePathComponent(segment))) return `/${PAGE_ROUTES.files}`;
|
|
172
|
+
return `/${PAGE_ROUTES.files}/${segments.map(encodeURIComponent).join("/")}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function buildWikiTarget(target: Extract<NavigateTarget, { view: typeof NOTIFICATION_VIEWS.wiki }>): string {
|
|
176
|
+
// Slug rides as a path component → dot-segment-check it. The
|
|
177
|
+
// anchor lives in the URL fragment, which doesn't participate in
|
|
178
|
+
// path normalization, so it doesn't need the same guard.
|
|
179
|
+
const slug = target.slug && isSafePathComponent(target.slug) ? `/pages/${encodeURIComponent(target.slug)}` : "";
|
|
180
|
+
const hash = target.anchor ? `#${encodeURIComponent(target.anchor)}` : "";
|
|
181
|
+
return `/${PAGE_ROUTES.wiki}${slug}${hash}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildSingleSegmentTarget(view: string, segment: string | undefined): string {
|
|
185
|
+
// Helper for views with a single optional path component: todos
|
|
186
|
+
// (itemId), automations (taskId), sources (slug). Unsafe values
|
|
187
|
+
// fall back to the view's index — a soft-fail since these views
|
|
188
|
+
// all have a usable index page.
|
|
189
|
+
if (segment && isSafePathComponent(segment)) return `/${view}/${encodeURIComponent(segment)}`;
|
|
190
|
+
return `/${view}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildNavigateTarget(target: NavigateTarget): string | undefined {
|
|
194
|
+
switch (target.view) {
|
|
195
|
+
case NOTIFICATION_VIEWS.chat:
|
|
196
|
+
return buildChatTarget(target);
|
|
197
|
+
case NOTIFICATION_VIEWS.todos:
|
|
198
|
+
return buildSingleSegmentTarget(PAGE_ROUTES.todos, target.itemId);
|
|
199
|
+
case NOTIFICATION_VIEWS.calendar:
|
|
200
|
+
return `/${PAGE_ROUTES.calendar}`;
|
|
201
|
+
case NOTIFICATION_VIEWS.automations:
|
|
202
|
+
return buildSingleSegmentTarget(PAGE_ROUTES.automations, target.taskId);
|
|
203
|
+
case NOTIFICATION_VIEWS.sources:
|
|
204
|
+
return buildSingleSegmentTarget(PAGE_ROUTES.sources, target.slug);
|
|
205
|
+
case NOTIFICATION_VIEWS.files:
|
|
206
|
+
return buildFilesTarget(target);
|
|
207
|
+
case NOTIFICATION_VIEWS.wiki:
|
|
208
|
+
return buildWikiTarget(target);
|
|
209
|
+
default:
|
|
210
|
+
return undefined;
|
|
104
211
|
}
|
|
105
212
|
}
|
|
106
213
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
214
|
+
/** Flatten a typed `NotificationAction` to a relative URL string the
|
|
215
|
+
* bell can hand straight to `router.push`. Returns `undefined` for
|
|
216
|
+
* `action: { type: "none" }`, missing required fields (e.g. a chat
|
|
217
|
+
* navigate without `sessionId`), or unknown views. The engine's
|
|
218
|
+
* `navigateTarget` validation requires same-origin paths starting
|
|
219
|
+
* with a single "/", which every branch below satisfies. */
|
|
220
|
+
export function legacyActionToNavigateTarget(action: NotificationAction | undefined): string | undefined {
|
|
221
|
+
if (!action || action.type !== NOTIFICATION_ACTION_TYPES.navigate) return undefined;
|
|
222
|
+
return buildNavigateTarget(action.target);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Publish ─────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Host-only entry point for firing notifications.
|
|
229
|
+
*
|
|
230
|
+
* **Plugins MUST NOT call this directly.** Plugin code (anything under
|
|
231
|
+
* `packages/*-plugin/`) MUST publish through `runtime.notifier.publish`
|
|
232
|
+
* (see `server/notifier/runtime-api.ts`), which auto-binds `pluginPkg`
|
|
233
|
+
* to the calling plugin so plugins cannot impersonate each other.
|
|
234
|
+
*
|
|
235
|
+
* This wrapper exists for host-side callers (`server/agent/`,
|
|
236
|
+
* `server/api/routes/`, `server/workspace/`, `server/plugins/diagnostics.ts`)
|
|
237
|
+
* that don't have a `PluginRuntime` to hand. It forwards into
|
|
238
|
+
* `notifier.publish` with `lifecycle: "fyi"` and stashes legacy fields
|
|
239
|
+
* on `pluginData` so the bell can preserve icon / i18n / transport
|
|
240
|
+
* semantics. Bridge + macOS push are owned by separate adapters
|
|
241
|
+
* subscribed to the `notifier` pubsub channel, not by this function.
|
|
242
|
+
*/
|
|
243
|
+
export function publishNotification(opts: PublishNotificationOpts): void {
|
|
244
|
+
const legacyId = opts.id ?? makeUuid();
|
|
245
|
+
const action: NotificationAction = opts.action ?? { type: NOTIFICATION_ACTION_TYPES.none };
|
|
246
|
+
const pluginData: LegacyNotifierPluginData = {
|
|
247
|
+
legacy: true,
|
|
248
|
+
legacyId,
|
|
249
|
+
kind: opts.kind,
|
|
250
|
+
priority: opts.priority ?? NOTIFICATION_PRIORITIES.normal,
|
|
251
|
+
action,
|
|
252
|
+
i18n: opts.i18n,
|
|
253
|
+
transportId: opts.transportId,
|
|
254
|
+
sessionId: opts.sessionId,
|
|
255
|
+
};
|
|
256
|
+
// Fire-and-forget — the engine queues writes through its own
|
|
257
|
+
// coordinator. A persistence failure logs but never throws to the
|
|
258
|
+
// caller (matches the legacy try/catch contract that protected
|
|
259
|
+
// call sites like endRun).
|
|
260
|
+
notifierPublish({
|
|
261
|
+
pluginPkg: legacyKindToPluginPkg(opts.kind),
|
|
262
|
+
severity: legacyPriorityToSeverity(opts.priority),
|
|
263
|
+
lifecycle: "fyi",
|
|
264
|
+
title: opts.title,
|
|
265
|
+
body: opts.body,
|
|
266
|
+
navigateTarget: legacyActionToNavigateTarget(action),
|
|
267
|
+
pluginData,
|
|
268
|
+
}).catch((err) => {
|
|
269
|
+
log.warn("notifications", "publish failed", { error: String(err), legacyId, kind: opts.kind });
|
|
270
|
+
});
|
|
112
271
|
}
|
|
113
272
|
|
|
114
273
|
// ── Legacy test notification (kept for PoC endpoint) ────────────
|
|
115
274
|
|
|
116
275
|
export const DEFAULT_NOTIFICATION_MESSAGE = "Test notification";
|
|
117
276
|
export const DEFAULT_NOTIFICATION_TRANSPORT_ID = "cli";
|
|
118
|
-
export const DEFAULT_NOTIFICATION_CHAT_ID = "notifications";
|
|
119
277
|
|
|
120
278
|
export interface ScheduleNotificationOptions {
|
|
121
279
|
message?: string;
|
|
122
280
|
body?: string;
|
|
123
281
|
delaySeconds?: number;
|
|
124
282
|
transportId?: string;
|
|
125
|
-
chatId?: string;
|
|
126
283
|
// Optional deep-link action — lets dev-side callers fire a
|
|
127
284
|
// notification that navigates to a specific permalink when
|
|
128
285
|
// clicked. Without this the fired notification has no click
|
|
@@ -140,10 +297,13 @@ export interface ScheduledNotification {
|
|
|
140
297
|
cancel: () => void;
|
|
141
298
|
}
|
|
142
299
|
|
|
143
|
-
|
|
300
|
+
/** PoC: schedules one `publishNotification` after a delay. Bridge
|
|
301
|
+
* push is no longer sent inline here — the bridge adapter (subscribed
|
|
302
|
+
* to the notifier channel) fans out to bridges based on the entry's
|
|
303
|
+
* `pluginData.transportId`. */
|
|
304
|
+
export function scheduleTestNotification(opts: ScheduleNotificationOptions): ScheduledNotification {
|
|
144
305
|
const message = opts.message ?? DEFAULT_NOTIFICATION_MESSAGE;
|
|
145
306
|
const transportId = opts.transportId ?? DEFAULT_NOTIFICATION_TRANSPORT_ID;
|
|
146
|
-
const chatId = opts.chatId ?? DEFAULT_NOTIFICATION_CHAT_ID;
|
|
147
307
|
const delaySeconds = clampDelay(opts.delaySeconds);
|
|
148
308
|
const delayMs = delaySeconds * ONE_SECOND_MS;
|
|
149
309
|
const kind = opts.kind ?? NOTIFICATION_KINDS.push;
|
|
@@ -156,13 +316,9 @@ export function scheduleTestNotification(opts: ScheduleNotificationOptions, lega
|
|
|
156
316
|
title: message,
|
|
157
317
|
body: opts.body,
|
|
158
318
|
priority: NOTIFICATION_PRIORITIES.normal,
|
|
159
|
-
// When the caller supplied an action, pass it through so the
|
|
160
|
-
// bell clicks into the requested permalink. Otherwise leave
|
|
161
|
-
// it undefined so publishNotification falls back to the
|
|
162
|
-
// "navigate: none" default.
|
|
163
319
|
action: opts.action,
|
|
320
|
+
transportId,
|
|
164
321
|
});
|
|
165
|
-
legacyDeps.pushToBridge(transportId, chatId, message);
|
|
166
322
|
}, delayMs);
|
|
167
323
|
|
|
168
324
|
return {
|
|
@@ -3,7 +3,7 @@ import { Server as IOServer } from "socket.io";
|
|
|
3
3
|
|
|
4
4
|
export interface IPubSub {
|
|
5
5
|
/** Publish data to all clients subscribed to this channel. */
|
|
6
|
-
publish(channel: string, data: unknown)
|
|
6
|
+
publish: (channel: string, data: unknown) => void;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// Channel names are treated as socket.io rooms — one room per
|