mulmoclaude 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<img v-if="isImage" :src="rawUrl" :alt="basename" :title="basename" :class="imgClass" data-testid="sent-attachment-chip" :data-variant="variant" />
|
|
3
|
+
<div v-else :title="basename" :class="fileChipClass" data-testid="sent-attachment-chip" :data-variant="variant">
|
|
4
|
+
<span class="material-icons" :class="[iconColor, fileIconSize]">{{ icon }}</span>
|
|
5
|
+
<span class="truncate">{{ basename }}</span>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { computed } from "vue";
|
|
11
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
12
|
+
|
|
13
|
+
// "thumb" — small, used by the sidebar Preview (single-mode chat list).
|
|
14
|
+
// "block" — fills the canvas content width, used by the in-bubble
|
|
15
|
+
// View on the canvas (stack and single mode).
|
|
16
|
+
type Variant = "thumb" | "block";
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
/** Workspace-relative path (e.g. `data/attachments/2026/04/<id>.png`). */
|
|
21
|
+
path: string;
|
|
22
|
+
variant?: Variant;
|
|
23
|
+
}>(),
|
|
24
|
+
{ variant: "thumb" },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"]);
|
|
28
|
+
|
|
29
|
+
const ext = computed(() => {
|
|
30
|
+
const dot = props.path.lastIndexOf(".");
|
|
31
|
+
return dot >= 0 ? props.path.slice(dot).toLowerCase() : "";
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const basename = computed(() => {
|
|
35
|
+
const slash = props.path.lastIndexOf("/");
|
|
36
|
+
return slash >= 0 ? props.path.slice(slash + 1) : props.path;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const isImage = computed(() => IMAGE_EXTS.has(ext.value));
|
|
40
|
+
|
|
41
|
+
// `/api/files/raw` is auth-exempt for `<img src>` use (see
|
|
42
|
+
// server/index.ts:114) so the path round-trips without a bearer token.
|
|
43
|
+
const rawUrl = computed(() => `${API_ROUTES.files.raw}?path=${encodeURIComponent(props.path)}`);
|
|
44
|
+
|
|
45
|
+
const imgClass = computed(() =>
|
|
46
|
+
props.variant === "block"
|
|
47
|
+
? // Canvas: stretch to the bubble's content width, cap height so
|
|
48
|
+
// tall images don't dominate. `object-contain` preserves aspect
|
|
49
|
+
// ratio when the natural ratio would push past max-h.
|
|
50
|
+
"block w-full max-h-[60vh] object-contain rounded-lg border border-gray-200 bg-gray-50"
|
|
51
|
+
: // Sidebar preview: small thumbnail.
|
|
52
|
+
"block max-h-16 max-w-24 object-contain rounded border border-gray-300 bg-white",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const fileChipClass = computed(() => {
|
|
56
|
+
const base = "inline-flex items-center gap-1.5 border border-gray-300 bg-white rounded text-gray-700";
|
|
57
|
+
return props.variant === "block" ? `${base} px-3 py-2 text-sm max-w-full` : `${base} px-2 py-1 text-xs`;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const fileIconSize = computed(() => (props.variant === "block" ? "text-xl" : "text-base"));
|
|
61
|
+
|
|
62
|
+
const icon = computed(() => {
|
|
63
|
+
switch (ext.value) {
|
|
64
|
+
case ".pdf":
|
|
65
|
+
return "picture_as_pdf";
|
|
66
|
+
case ".docx":
|
|
67
|
+
return "description";
|
|
68
|
+
case ".xlsx":
|
|
69
|
+
case ".csv":
|
|
70
|
+
return "table_chart";
|
|
71
|
+
case ".pptx":
|
|
72
|
+
return "slideshow";
|
|
73
|
+
case ".txt":
|
|
74
|
+
case ".md":
|
|
75
|
+
return "article";
|
|
76
|
+
case ".json":
|
|
77
|
+
case ".xml":
|
|
78
|
+
case ".yaml":
|
|
79
|
+
case ".yml":
|
|
80
|
+
case ".toml":
|
|
81
|
+
return "data_object";
|
|
82
|
+
default:
|
|
83
|
+
return "insert_drive_file";
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const iconColor = computed(() => {
|
|
88
|
+
switch (ext.value) {
|
|
89
|
+
case ".pdf":
|
|
90
|
+
return "text-red-500";
|
|
91
|
+
case ".docx":
|
|
92
|
+
return "text-blue-500";
|
|
93
|
+
case ".xlsx":
|
|
94
|
+
case ".csv":
|
|
95
|
+
return "text-green-600";
|
|
96
|
+
case ".pptx":
|
|
97
|
+
return "text-orange-500";
|
|
98
|
+
default:
|
|
99
|
+
return "text-gray-500";
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
</script>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
:label="t(`sessionHistoryPanel.filters.${f}`)"
|
|
15
15
|
:count="f === HISTORY_FILTERS.all ? undefined : countByOrigin(f)"
|
|
16
16
|
:data-testid="`session-filter-${f}`"
|
|
17
|
-
@click="
|
|
17
|
+
@click="toggleFilter(f)"
|
|
18
18
|
/>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
@@ -44,13 +44,53 @@
|
|
|
44
44
|
@keydown.space.prevent.self="(e) => !e.repeat && emit('loadSession', session.id)"
|
|
45
45
|
>
|
|
46
46
|
<!-- Timestamp pill straddling the top border, mirroring the
|
|
47
|
-
SessionSidebar card design. The
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
SessionSidebar card design. The kebab "..." button sits
|
|
48
|
+
next to it on the same border line — clicking opens a
|
|
49
|
+
popover with delete + bookmark actions. The running
|
|
50
|
+
indicator still renders inline in the meta line below
|
|
51
|
+
(it's a status, not a time); unread is signalled through
|
|
52
|
+
previewClasses (bold text); bookmark state is signalled
|
|
53
|
+
via the green role icon. -->
|
|
54
|
+
<div class="absolute top-0 right-6 -translate-y-1/2 flex items-center gap-1 bg-white px-1 leading-none">
|
|
55
|
+
<span class="text-[10px] text-gray-400 pointer-events-none">{{ formatDate(session.updatedAt) }}</span>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
class="flex items-center justify-center px-0.5 border border-gray-300 rounded-md text-gray-400 hover:text-gray-700 hover:border-gray-400 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-400"
|
|
59
|
+
:aria-label="t('sessionHistoryPanel.rowMenuAria')"
|
|
60
|
+
:data-testid="`session-row-menu-${session.id}`"
|
|
61
|
+
@click.stop="toggleMenu(session.id)"
|
|
62
|
+
@keydown.enter.stop
|
|
63
|
+
@keydown.space.stop
|
|
64
|
+
>
|
|
65
|
+
<span class="material-icons !text-[14px] leading-none" aria-hidden="true">more_horiz</span>
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div
|
|
69
|
+
v-if="openMenuId === session.id"
|
|
70
|
+
class="absolute top-2 right-2 z-10 min-w-[140px] rounded border border-gray-200 bg-white shadow-md py-1 text-xs"
|
|
71
|
+
role="menu"
|
|
72
|
+
:data-testid="`session-row-menu-popover-${session.id}`"
|
|
73
|
+
@click.stop
|
|
74
|
+
>
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
role="menuitem"
|
|
78
|
+
class="block w-full text-left px-3 py-1.5 hover:bg-gray-100"
|
|
79
|
+
:data-testid="`session-row-bookmark-${session.id}`"
|
|
80
|
+
@click.stop="onToggleBookmark(session)"
|
|
81
|
+
>
|
|
82
|
+
{{ session.isBookmarked ? t("sessionHistoryPanel.unbookmark") : t("sessionHistoryPanel.bookmark") }}
|
|
83
|
+
</button>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
role="menuitem"
|
|
87
|
+
class="block w-full text-left px-3 py-1.5 text-red-600 hover:bg-red-50"
|
|
88
|
+
:data-testid="`session-row-delete-${session.id}`"
|
|
89
|
+
@click.stop="onDelete(session)"
|
|
90
|
+
>
|
|
91
|
+
{{ t("sessionHistoryPanel.delete") }}
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
54
94
|
<div class="flex items-center gap-1.5">
|
|
55
95
|
<SessionRoleIcon :session="session" :roles="roles" size="sm" />
|
|
56
96
|
<p class="truncate flex-1 min-w-0" :class="previewClasses(session)">
|
|
@@ -71,7 +111,7 @@
|
|
|
71
111
|
</template>
|
|
72
112
|
|
|
73
113
|
<script setup lang="ts">
|
|
74
|
-
import { ref, computed } from "vue";
|
|
114
|
+
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|
75
115
|
import { useI18n } from "vue-i18n";
|
|
76
116
|
import type { Role } from "../config/roles";
|
|
77
117
|
import type { SessionSummary, SessionOrigin } from "../types/session";
|
|
@@ -83,10 +123,11 @@ import FilterChip from "./FilterChip.vue";
|
|
|
83
123
|
|
|
84
124
|
const { t } = useI18n();
|
|
85
125
|
|
|
86
|
-
// `unread`
|
|
87
|
-
// shows every
|
|
88
|
-
// the user expectation that
|
|
89
|
-
// needs my attention?") rather than
|
|
126
|
+
// `unread` and `bookmarked` are mutually exclusive with origin pills —
|
|
127
|
+
// selecting either shows every matching session regardless of origin,
|
|
128
|
+
// matching the user expectation that those are the primary questions
|
|
129
|
+
// ("what needs my attention?", "what did I save?") rather than origin
|
|
130
|
+
// sub-filters.
|
|
90
131
|
|
|
91
132
|
const props = defineProps<{
|
|
92
133
|
sessions: SessionSummary[];
|
|
@@ -98,6 +139,8 @@ const props = defineProps<{
|
|
|
98
139
|
|
|
99
140
|
const emit = defineEmits<{
|
|
100
141
|
loadSession: [id: string];
|
|
142
|
+
toggleBookmark: [id: string, bookmarked: boolean];
|
|
143
|
+
deleteSession: [id: string];
|
|
101
144
|
}>();
|
|
102
145
|
|
|
103
146
|
const root = ref<HTMLDivElement | null>(null);
|
|
@@ -115,16 +158,25 @@ function originOf(session: SessionSummary): SessionOrigin {
|
|
|
115
158
|
return session.origin ?? SESSION_ORIGINS.human;
|
|
116
159
|
}
|
|
117
160
|
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
function matchesFilter(session: SessionSummary, filter: HistoryFilter): boolean {
|
|
162
|
+
if (filter === HISTORY_FILTERS.all) return true;
|
|
163
|
+
if (filter === HISTORY_FILTERS.unread) return session.hasUnread === true;
|
|
164
|
+
if (filter === HISTORY_FILTERS.bookmarked) return session.isBookmarked === true;
|
|
165
|
+
return originOf(session) === filter;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const filteredSessions = computed(() => props.sessions.filter((session) => matchesFilter(session, activeFilter.value)));
|
|
169
|
+
|
|
170
|
+
// Mirror Wiki's toggleTagFilter (plugins/wiki/View.vue): clicking the
|
|
171
|
+
// already-active chip resets to `all`. The `all` chip itself is a
|
|
172
|
+
// no-op when active — there's nothing to "deselect" back to.
|
|
173
|
+
function toggleFilter(filter: HistoryFilter): void {
|
|
174
|
+
activeFilter.value = activeFilter.value === filter ? HISTORY_FILTERS.all : filter;
|
|
175
|
+
}
|
|
123
176
|
|
|
124
177
|
function countByOrigin(filterKey: HistoryFilter): number {
|
|
125
178
|
if (filterKey === HISTORY_FILTERS.all) return props.sessions.length;
|
|
126
|
-
|
|
127
|
-
return props.sessions.filter((session) => originOf(session) === filterKey).length;
|
|
179
|
+
return props.sessions.filter((session) => matchesFilter(session, filterKey)).length;
|
|
128
180
|
}
|
|
129
181
|
|
|
130
182
|
function isSessionRunning(session: SessionSummary): boolean {
|
|
@@ -144,4 +196,41 @@ function previewClasses(session: SessionSummary): string {
|
|
|
144
196
|
if (isSessionUnread(session)) return "text-gray-900 font-bold";
|
|
145
197
|
return "text-gray-700";
|
|
146
198
|
}
|
|
199
|
+
|
|
200
|
+
// ── Row action menu ─────────────────────────────────────────
|
|
201
|
+
//
|
|
202
|
+
// Only one popover is open at a time, tracked by session id. A
|
|
203
|
+
// document-level click listener closes it on any outside click; the
|
|
204
|
+
// kebab button and popover stop propagation so clicks inside don't
|
|
205
|
+
// trigger the closer (or the row's load-session handler).
|
|
206
|
+
|
|
207
|
+
const openMenuId = ref<string | null>(null);
|
|
208
|
+
|
|
209
|
+
function toggleMenu(sessionId: string): void {
|
|
210
|
+
openMenuId.value = openMenuId.value === sessionId ? null : sessionId;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function closeMenu(): void {
|
|
214
|
+
openMenuId.value = null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function onToggleBookmark(session: SessionSummary): void {
|
|
218
|
+
emit("toggleBookmark", session.id, !session.isBookmarked);
|
|
219
|
+
closeMenu();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function onDelete(session: SessionSummary): void {
|
|
223
|
+
const ok = window.confirm(t("sessionHistoryPanel.deleteConfirm", { preview: session.preview || t("sessionHistoryPanel.noMessages") }));
|
|
224
|
+
if (!ok) return;
|
|
225
|
+
emit("deleteSession", session.id);
|
|
226
|
+
closeMenu();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
onMounted(() => {
|
|
230
|
+
document.addEventListener("click", closeMenu);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
onBeforeUnmount(() => {
|
|
234
|
+
document.removeEventListener("click", closeMenu);
|
|
235
|
+
});
|
|
147
236
|
</script>
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
SessionHistoryPanel. Visual rules:
|
|
4
4
|
- Running → yellow with a slow spin (the one place we keep
|
|
5
5
|
colour because the state is load-bearing)
|
|
6
|
-
-
|
|
7
|
-
|
|
6
|
+
- Bookmarked → green (user-set, sits below "running" because
|
|
7
|
+
status beats user-state)
|
|
8
|
+
- Unread (not running, not bookmarked) → darker gray so the row
|
|
9
|
+
reads as "something to look at"
|
|
8
10
|
- Default → light gray
|
|
9
11
|
- Origin (scheduler / skill / bridge) → tiny B&W badge on the
|
|
10
12
|
top-right. Human / unknown origin → no badge. -->
|
|
@@ -47,6 +49,7 @@ const iconSizeClass = computed(() => (props.size === "sm" ? "text-sm" : "text-ba
|
|
|
47
49
|
|
|
48
50
|
const stateClass = computed(() => {
|
|
49
51
|
if (props.session.isRunning) return "text-yellow-400";
|
|
52
|
+
if (props.session.isBookmarked) return "text-green-600";
|
|
50
53
|
if (props.session.hasUnread) return "text-gray-900";
|
|
51
54
|
return "text-gray-400";
|
|
52
55
|
});
|
|
@@ -56,7 +59,7 @@ const spinClass = computed(() => (props.session.isRunning ? "animate-spin [anima
|
|
|
56
59
|
const glyph = computed(() => roleIcon(props.roles, props.session.roleId));
|
|
57
60
|
|
|
58
61
|
const originGlyph = computed(() => {
|
|
59
|
-
const origin = props.session
|
|
62
|
+
const { origin } = props.session;
|
|
60
63
|
if (!origin || origin === SESSION_ORIGINS.human) return "";
|
|
61
64
|
if (origin === SESSION_ORIGINS.scheduler) return "schedule";
|
|
62
65
|
if (origin === SESSION_ORIGINS.skill) return "build";
|
|
@@ -65,7 +68,7 @@ const originGlyph = computed(() => {
|
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
const originTooltip = computed(() => {
|
|
68
|
-
const origin = props.session
|
|
71
|
+
const { origin } = props.session;
|
|
69
72
|
if (!origin || origin === SESSION_ORIGINS.human) return "";
|
|
70
73
|
return t(`sessionTabBar.origin.${origin}`);
|
|
71
74
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<span v-if="sessionRoleIcon" class="material-icons text-xs leading-none">{{ sessionRoleIcon }}</span>
|
|
5
5
|
<span v-if="sessionRoleName" class="truncate">{{ sessionRoleName }}</span>
|
|
6
6
|
<div class="ml-auto flex items-center gap-0.5 shrink-0">
|
|
7
|
+
<CopyChatButton :results="results" :result-timestamps="resultTimestamps" :session-role-name="sessionRoleName" />
|
|
7
8
|
<button
|
|
8
9
|
type="button"
|
|
9
10
|
class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors"
|
|
@@ -55,16 +56,17 @@ import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
|
55
56
|
import { getPlugin } from "../tools";
|
|
56
57
|
import { formatSmartTime } from "../utils/format/date";
|
|
57
58
|
import CanvasViewToggle from "./CanvasViewToggle.vue";
|
|
59
|
+
import CopyChatButton from "./CopyChatButton.vue";
|
|
58
60
|
import type { LayoutMode } from "../utils/canvas/layoutMode";
|
|
59
61
|
|
|
60
62
|
const { t } = useI18n();
|
|
61
63
|
|
|
62
|
-
function sourceLabel(result: ToolResultComplete): string {
|
|
63
|
-
if (result.toolName === "text-response") return result.title ?? "Assistant";
|
|
64
|
-
return result.toolName;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
64
|
defineProps<{
|
|
65
|
+
// Already filtered to "what the user should see" by the parent's
|
|
66
|
+
// `sidebarResults` computed (see useSessionDerived.ts) — keyboard
|
|
67
|
+
// navigation, selection, and this render all consume the same
|
|
68
|
+
// visible-only list so a hidden result can't slip into selected
|
|
69
|
+
// state and leave the sidebar showing no highlight.
|
|
68
70
|
results: ToolResultComplete[];
|
|
69
71
|
selectedUuid: string | null;
|
|
70
72
|
resultTimestamps: Map<string, number>;
|
|
@@ -74,6 +76,18 @@ defineProps<{
|
|
|
74
76
|
showRightSidebar: boolean;
|
|
75
77
|
}>();
|
|
76
78
|
|
|
79
|
+
function sourceLabel(result: ToolResultComplete): string {
|
|
80
|
+
if (result.toolName === "text-response") return result.title ?? "Assistant";
|
|
81
|
+
// `action` lives on the persisted tool-result (see #670b40a5
|
|
82
|
+
// `feat(sidebar): use ToolResult.action for multi-feature labels`)
|
|
83
|
+
// but is not yet declared on `ToolResultComplete` in
|
|
84
|
+
// `gui-chat-protocol`. Cast to a local view so vue-tsc accepts
|
|
85
|
+
// the access; runtime is unchanged (it was already returning
|
|
86
|
+
// `undefined` for results that don't carry one).
|
|
87
|
+
const { action } = result as { action?: unknown };
|
|
88
|
+
return typeof action === "string" && action.length > 0 ? `${result.toolName}(${action})` : result.toolName;
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
const emit = defineEmits<{
|
|
78
92
|
select: [uuid: string];
|
|
79
93
|
activate: [];
|
|
@@ -86,8 +100,7 @@ defineExpose({ root });
|
|
|
86
100
|
</script>
|
|
87
101
|
|
|
88
102
|
<style scoped>
|
|
89
|
-
/*
|
|
90
|
-
Clicking a sidebar card should select the result, not follow links. */
|
|
103
|
+
/* Card click selects the result; rendered markdown links inside the preview must not navigate. */
|
|
91
104
|
:deep(a) {
|
|
92
105
|
pointer-events: none;
|
|
93
106
|
color: inherit;
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<script setup lang="ts">
|
|
32
32
|
import { useI18n } from "vue-i18n";
|
|
33
33
|
import type { Role } from "../config/roles";
|
|
34
|
-
import {
|
|
34
|
+
import type { SessionSummary } from "../types/session";
|
|
35
35
|
import { roleName } from "../utils/role/icon";
|
|
36
36
|
import SessionRoleIcon from "./SessionRoleIcon.vue";
|
|
37
37
|
|
|
@@ -294,15 +294,15 @@
|
|
|
294
294
|
import { computed, ref } from "vue";
|
|
295
295
|
import { useI18n } from "vue-i18n";
|
|
296
296
|
|
|
297
|
-
const { t } = useI18n();
|
|
298
|
-
|
|
299
297
|
// UI-local representation of a configured server. Matches
|
|
300
298
|
// server/config.ts#McpServerEntry. Re-declared in src/config/mcpTypes.ts
|
|
301
299
|
// to avoid a cross-module type import from the server package.
|
|
302
|
-
import {
|
|
300
|
+
import type { HttpSpec, StdioSpec, McpServerSpec, McpServerEntry } from "../config/mcpTypes";
|
|
303
301
|
import { MCP_CATALOG, requiredKeysOf, type McpCatalogEntry, type McpConfigField } from "../config/mcpCatalog";
|
|
304
302
|
import { interpolateMcpSpec } from "../utils/mcp/interpolateSpec";
|
|
305
303
|
|
|
304
|
+
const { t } = useI18n();
|
|
305
|
+
|
|
306
306
|
// Re-exported for callers that imported these from the SFC before the
|
|
307
307
|
// types moved out (#823 Phase 1).
|
|
308
308
|
export type { HttpSpec, StdioSpec, McpServerSpec, McpServerEntry };
|
|
@@ -350,7 +350,7 @@ function isInstalled(serverId: string): boolean {
|
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
function onCatalogToggle(entry: McpCatalogEntry, event: Event): void {
|
|
353
|
-
const checked =
|
|
353
|
+
const { checked } = event.target as HTMLInputElement;
|
|
354
354
|
if (checked) {
|
|
355
355
|
if (entry.configSchema.length > 0) {
|
|
356
356
|
openConfigForm(entry);
|
|
@@ -255,6 +255,7 @@ async function loadConfig(): Promise<void> {
|
|
|
255
255
|
mcp?: { servers: McpServerEntry[] };
|
|
256
256
|
}>(API_ROUTES.config.base);
|
|
257
257
|
// A newer open() has already started another load — drop this one.
|
|
258
|
+
// eslint-disable-next-line security/detect-possible-timing-attacks -- in-memory race-token guard, not an auth compare
|
|
258
259
|
if (token !== loadToken) return;
|
|
259
260
|
if (!response.ok) {
|
|
260
261
|
loadError.value = response.status === 0 ? response.error || "Network error" : `Failed to load settings (HTTP ${response.status})`;
|
|
@@ -264,6 +265,7 @@ async function loadConfig(): Promise<void> {
|
|
|
264
265
|
toolsSavedText.value = text;
|
|
265
266
|
mcpServers.value = response.data.mcp?.servers ?? [];
|
|
266
267
|
}
|
|
268
|
+
// eslint-disable-next-line security/detect-possible-timing-attacks -- same race-token guard as above
|
|
267
269
|
if (token === loadToken) loading.value = false;
|
|
268
270
|
}
|
|
269
271
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="flex items-center gap-2">
|
|
3
3
|
<button
|
|
4
4
|
type="button"
|
|
5
|
-
class="flex items-center gap-2 -my-1 -ml-1 py-1 pl-1 pr-
|
|
5
|
+
class="flex items-center gap-2 -my-1 -ml-1 py-1 pl-1 pr-1 rounded hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
|
|
6
6
|
data-testid="app-home-btn"
|
|
7
7
|
:title="t('sidebarHeader.home')"
|
|
8
8
|
:aria-label="t('sidebarHeader.home')"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<img :src="logoUrl" alt="" class="h-[50px] w-auto -my-3.5 -ml-3 rounded object-contain shrink-0" />
|
|
12
12
|
<!-- span, not h1: `<h1>` inside `<button>` is invalid HTML, and
|
|
13
13
|
the brand label here is a clickable logo, not a page heading. -->
|
|
14
|
-
<span data-testid="app-title" class="text-sm font-semibold text-gray-800
|
|
14
|
+
<span data-testid="app-title" class="text-sm font-semibold text-gray-800" :style="titleStyle">MulmoClaude</span>
|
|
15
15
|
</button>
|
|
16
16
|
<div class="flex gap-0.5">
|
|
17
17
|
<LockStatusPopup
|
|
@@ -21,7 +21,17 @@
|
|
|
21
21
|
@update:open="lockPopupOpen = $event"
|
|
22
22
|
@test-query="(q) => emit('testQuery', q)"
|
|
23
23
|
/>
|
|
24
|
-
<NotificationBell :force-close="lockPopupOpen" @
|
|
24
|
+
<NotificationBell :force-close="lockPopupOpen" @update:open="onNotificationOpen" />
|
|
25
|
+
<button
|
|
26
|
+
class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
27
|
+
data-testid="today-journal-btn"
|
|
28
|
+
:title="t('sidebarHeader.todayJournal')"
|
|
29
|
+
:aria-label="t('sidebarHeader.todayJournal')"
|
|
30
|
+
:disabled="todayJournalLoading"
|
|
31
|
+
@click="openLatestDaily"
|
|
32
|
+
>
|
|
33
|
+
<span class="material-icons">today</span>
|
|
34
|
+
</button>
|
|
25
35
|
<button
|
|
26
36
|
class="relative h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700"
|
|
27
37
|
data-testid="settings-btn"
|
|
@@ -47,7 +57,7 @@ import { useI18n } from "vue-i18n";
|
|
|
47
57
|
import LockStatusPopup from "./LockStatusPopup.vue";
|
|
48
58
|
import NotificationBell from "./NotificationBell.vue";
|
|
49
59
|
import { useClickOutside } from "../composables/useClickOutside";
|
|
50
|
-
import
|
|
60
|
+
import { useLatestDaily } from "../composables/useLatestDaily";
|
|
51
61
|
import logoUrl from "../assets/mulmo_bw.png";
|
|
52
62
|
|
|
53
63
|
const { t } = useI18n();
|
|
@@ -63,7 +73,6 @@ const props = withDefaults(
|
|
|
63
73
|
|
|
64
74
|
const emit = defineEmits<{
|
|
65
75
|
testQuery: [query: string];
|
|
66
|
-
notificationNavigate: [action: NotificationPayload["action"]];
|
|
67
76
|
openSettings: [];
|
|
68
77
|
home: [];
|
|
69
78
|
}>();
|
|
@@ -75,6 +84,8 @@ const emit = defineEmits<{
|
|
|
75
84
|
// attention signal is lost.
|
|
76
85
|
const settingsLabel = computed(() => (props.geminiAvailable ? t("sidebarHeader.settings") : t("sidebarHeader.settingsGeminiMissing")));
|
|
77
86
|
|
|
87
|
+
const { openLatestDaily, loading: todayJournalLoading } = useLatestDaily();
|
|
88
|
+
|
|
78
89
|
const lockPopupOpen = ref(false);
|
|
79
90
|
const lockPopup = ref<{
|
|
80
91
|
button: HTMLButtonElement | null;
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
<PageChatComposer
|
|
267
267
|
v-if="mode === 'page'"
|
|
268
268
|
:placeholder="t('pluginManageSource.chatPlaceholder')"
|
|
269
|
-
|
|
269
|
+
prepend-text="Before answering, read config/helps/sources.md for source-management conventions."
|
|
270
270
|
:suggestions="SOURCE_SUGGESTIONS"
|
|
271
271
|
test-id-prefix="sources-page-chat"
|
|
272
272
|
/>
|
|
@@ -281,6 +281,7 @@ import DOMPurify from "dompurify";
|
|
|
281
281
|
import type { ManageSourceData, RebuildSummary, Source } from "../plugins/manageSource/index";
|
|
282
282
|
import { apiGet, apiPost, apiDelete } from "../utils/api";
|
|
283
283
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
284
|
+
import { buildRouteUrl } from "../plugins/meta-types";
|
|
284
285
|
import { SOURCE_FILTER_KEYS, countByFilter, matchesSourceFilter, type SourceFilterKey } from "../utils/sources/filter";
|
|
285
286
|
import FilterChip from "./FilterChip.vue";
|
|
286
287
|
import PageChatComposer from "./PageChatComposer.vue";
|
|
@@ -312,6 +313,7 @@ const props = defineProps<{
|
|
|
312
313
|
}>();
|
|
313
314
|
|
|
314
315
|
const localSources = ref<Source[] | null>(props.initialData?.sources ?? null);
|
|
316
|
+
const sources = computed<Source[]>(() => localSources.value ?? []);
|
|
315
317
|
const lastRebuild = ref<RebuildSummary | null>(props.initialData?.lastRebuild ?? null);
|
|
316
318
|
const highlightSlugLocal = ref<string | null>(props.initialData?.highlightSlug ?? null);
|
|
317
319
|
const actionMessage = ref("");
|
|
@@ -431,7 +433,7 @@ function buildRegisterPayload(input: DraftState): RegisterPayload | BuildError {
|
|
|
431
433
|
}
|
|
432
434
|
let hostname: string;
|
|
433
435
|
try {
|
|
434
|
-
hostname = new URL(primary)
|
|
436
|
+
({ hostname } = new URL(primary));
|
|
435
437
|
} catch {
|
|
436
438
|
return { errorKey: "pluginManageSource.errRssUrlInvalid" };
|
|
437
439
|
}
|
|
@@ -479,7 +481,7 @@ async function commitAdd(): Promise<void> {
|
|
|
479
481
|
}
|
|
480
482
|
draftError.value = "";
|
|
481
483
|
busy.value = "add";
|
|
482
|
-
const response = await apiPost<unknown>(API_ROUTES.sources.create, payload);
|
|
484
|
+
const response = await apiPost<unknown>(API_ROUTES.sources.create.url, payload);
|
|
483
485
|
if (!response.ok) {
|
|
484
486
|
draftError.value = response.error || t("pluginManageSource.flashRegisterFailed");
|
|
485
487
|
busy.value = null;
|
|
@@ -579,7 +581,7 @@ async function installPreset(preset: Preset): Promise<void> {
|
|
|
579
581
|
}
|
|
580
582
|
const failures: string[] = [];
|
|
581
583
|
for (const entry of toRegister) {
|
|
582
|
-
const response = await apiPost<unknown>(API_ROUTES.sources.create, {
|
|
584
|
+
const response = await apiPost<unknown>(API_ROUTES.sources.create.url, {
|
|
583
585
|
slug: entry.slug,
|
|
584
586
|
title: entry.title,
|
|
585
587
|
url: entry.url,
|
|
@@ -620,7 +622,7 @@ async function installPreset(preset: Preset): Promise<void> {
|
|
|
620
622
|
// Rebuild step extracted so commitAdd can chain it without recursing
|
|
621
623
|
// into rebuild()'s own busy-state machine.
|
|
622
624
|
async function rebuildInline(): Promise<void> {
|
|
623
|
-
const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild);
|
|
625
|
+
const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild.url);
|
|
624
626
|
if (!response.ok) {
|
|
625
627
|
flash(t("pluginManageSource.flashRegisterSucceededRebuildFailed", { error: response.error }), true);
|
|
626
628
|
return;
|
|
@@ -631,7 +633,6 @@ async function rebuildInline(): Promise<void> {
|
|
|
631
633
|
await loadBrief(summary.isoDate);
|
|
632
634
|
}
|
|
633
635
|
|
|
634
|
-
const sources = computed<Source[]>(() => localSources.value ?? []);
|
|
635
636
|
const highlightSlug = computed(() => highlightSlugLocal.value);
|
|
636
637
|
|
|
637
638
|
// Filter chip state (#768). Single-select. `all` is the implicit
|
|
@@ -662,6 +663,10 @@ function filterChipLabel(key: SourceFilterKey): string {
|
|
|
662
663
|
return t("pluginManageSource.filter.scheduleWeekly");
|
|
663
664
|
case "schedule:manual":
|
|
664
665
|
return t("pluginManageSource.filter.scheduleManual");
|
|
666
|
+
default: {
|
|
667
|
+
const exhaustive: never = key;
|
|
668
|
+
throw new Error(`unreachable SourceFilterKey: ${String(exhaustive)}`);
|
|
669
|
+
}
|
|
665
670
|
}
|
|
666
671
|
}
|
|
667
672
|
|
|
@@ -705,6 +710,10 @@ function kindLabel(kind: Source["fetcherKind"]): string {
|
|
|
705
710
|
return t("pluginManageSource.kindGithubIss");
|
|
706
711
|
case "arxiv":
|
|
707
712
|
return t("pluginManageSource.kindArxiv");
|
|
713
|
+
default: {
|
|
714
|
+
const exhaustive: never = kind;
|
|
715
|
+
throw new Error(`unreachable fetcherKind: ${String(exhaustive)}`);
|
|
716
|
+
}
|
|
708
717
|
}
|
|
709
718
|
}
|
|
710
719
|
|
|
@@ -718,6 +727,10 @@ function kindBadgeClass(kind: Source["fetcherKind"]): string {
|
|
|
718
727
|
return "bg-indigo-100 text-indigo-700";
|
|
719
728
|
case "arxiv":
|
|
720
729
|
return "bg-emerald-100 text-emerald-700";
|
|
730
|
+
default: {
|
|
731
|
+
const exhaustive: never = kind;
|
|
732
|
+
throw new Error(`unreachable fetcherKind: ${String(exhaustive)}`);
|
|
733
|
+
}
|
|
721
734
|
}
|
|
722
735
|
}
|
|
723
736
|
|
|
@@ -733,7 +746,7 @@ function flash(message: string, isError = false): void {
|
|
|
733
746
|
// initial page-mode load) can keep that error visible in the UI
|
|
734
747
|
// rather than rely on the transient flash toast.
|
|
735
748
|
async function refreshList(): Promise<string | null> {
|
|
736
|
-
const response = await apiGet<{ sources: Source[] }>(API_ROUTES.sources.list);
|
|
749
|
+
const response = await apiGet<{ sources: Source[] }>(API_ROUTES.sources.list.url);
|
|
737
750
|
if (!response.ok) {
|
|
738
751
|
flash(t("pluginManageSource.flashRefreshListFailed", { error: response.error }), true);
|
|
739
752
|
return response.error || t("pluginManageSource.initialLoadFailed");
|
|
@@ -745,7 +758,7 @@ async function refreshList(): Promise<string | null> {
|
|
|
745
758
|
async function remove(slug: string): Promise<void> {
|
|
746
759
|
if (!confirm(t("pluginManageSource.confirmRemove", { slug }))) return;
|
|
747
760
|
busy.value = slug;
|
|
748
|
-
const response = await apiDelete<unknown>(API_ROUTES.sources.remove
|
|
761
|
+
const response = await apiDelete<unknown>(buildRouteUrl(API_ROUTES.sources.remove, { slug }));
|
|
749
762
|
busy.value = null;
|
|
750
763
|
if (!response.ok) {
|
|
751
764
|
flash(t("pluginManageSource.flashRemoveFailed", { error: response.error }), true);
|
|
@@ -757,7 +770,7 @@ async function remove(slug: string): Promise<void> {
|
|
|
757
770
|
|
|
758
771
|
async function rebuild(): Promise<void> {
|
|
759
772
|
busy.value = "rebuild";
|
|
760
|
-
const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild);
|
|
773
|
+
const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild.url);
|
|
761
774
|
if (!response.ok) {
|
|
762
775
|
flash(t("pluginManageSource.flashRebuildFailed", { error: response.error }), true);
|
|
763
776
|
busy.value = null;
|
|
@@ -811,6 +824,7 @@ async function loadBrief(isoDate: string): Promise<void> {
|
|
|
811
824
|
const relPath = dailyPathFor(isoDate);
|
|
812
825
|
briefFilePath.value = relPath;
|
|
813
826
|
const response = await apiGet<{ content?: string; kind?: string }>(API_ROUTES.files.content, { path: relPath });
|
|
827
|
+
// eslint-disable-next-line security/detect-possible-timing-attacks -- in-memory race-token guard, not an auth compare
|
|
814
828
|
if (token !== briefLoadToken) return;
|
|
815
829
|
if (!response.ok) {
|
|
816
830
|
if (response.status === 404) {
|