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
|
@@ -3,12 +3,14 @@ import { copyFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { log } from "../system/logger/index.js";
|
|
6
|
-
import { EAGER_WORKSPACE_DIRS,
|
|
7
|
-
import {
|
|
6
|
+
import { EAGER_WORKSPACE_DIRS, WORKSPACE_PATHS, workspacePath } from "./paths.js";
|
|
7
|
+
import { readWorkspaceTextSync, writeWorkspaceTextSync } from "../utils/files/workspace-io.js";
|
|
8
8
|
import { loadCustomDirs, ensureCustomDirs } from "./custom-dirs.js";
|
|
9
|
+
import { syncPresetSkills } from "./skills-preset.js";
|
|
9
10
|
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const TEMPLATES_DIR = path.join(__dirname, "helps");
|
|
13
|
+
const SKILLS_PRESET_SOURCE_DIR = path.join(__dirname, "skills-preset");
|
|
12
14
|
|
|
13
15
|
// Re-exported so existing callers (`import { workspacePath } from
|
|
14
16
|
// "./workspace.js"`) keep working. See workspace-paths.ts for the
|
|
@@ -24,10 +26,14 @@ export function initWorkspace(): string {
|
|
|
24
26
|
mkdirSync(WORKSPACE_PATHS[key], { recursive: true });
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// Ensure the typed-memory directory exists (#1029). Individual
|
|
30
|
+
// entry files are written by the agent or by the legacy-memory
|
|
31
|
+
// migration; init just guarantees the directory is there so the
|
|
32
|
+
// reader and migration both have a place to write to. The legacy
|
|
33
|
+
// `conversations/memory.md` is no longer auto-created — migration
|
|
34
|
+
// converts it on first start and the new layout becomes the source
|
|
35
|
+
// of truth thereafter.
|
|
36
|
+
mkdirSync(WORKSPACE_PATHS.memoryDir, { recursive: true });
|
|
31
37
|
|
|
32
38
|
// Always sync all files from server/helps/ into workspace/helps/
|
|
33
39
|
mkdirSync(WORKSPACE_PATHS.helps, { recursive: true });
|
|
@@ -35,15 +41,23 @@ export function initWorkspace(): string {
|
|
|
35
41
|
copyFileSync(path.join(TEMPLATES_DIR, file), path.join(WORKSPACE_PATHS.helps, file));
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
// Sync preset skills (#1210) from server/workspace/skills-preset/
|
|
45
|
+
// into <workspaceRoot>/.claude/skills/. Only `mc-*` slugs are
|
|
46
|
+
// touched — user-authored skills coexist untouched. Retired
|
|
47
|
+
// presets (no longer in source) are removed from the workspace.
|
|
48
|
+
syncPresetSkills({
|
|
49
|
+
sourceDir: SKILLS_PRESET_SOURCE_DIR,
|
|
50
|
+
destDir: WORKSPACE_PATHS.claudeSkills,
|
|
51
|
+
onInfo: (message, data) => log.info("skills-preset", message, data),
|
|
52
|
+
onWarn: (message, data) => log.warn("skills-preset", message, data),
|
|
53
|
+
});
|
|
54
|
+
|
|
38
55
|
// Create .gitignore if missing. The workspace is a git repo for
|
|
39
56
|
// version-tracking user data, but cloned dev repos under github/
|
|
40
|
-
// have their own .git and shouldn't be committed (#256).
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
["# Cloned repositories have their own .git — don't nest", "github/", "", "# Auth token (regenerated each startup)", ".session-token", ""].join("\n"),
|
|
45
|
-
);
|
|
46
|
-
}
|
|
57
|
+
// have their own .git and shouldn't be committed (#256). Runtime
|
|
58
|
+
// files (`.session-token`, `.server-port`) are regenerated on
|
|
59
|
+
// every startup and should never be committed (#917).
|
|
60
|
+
ensureWorkspaceGitignore();
|
|
47
61
|
|
|
48
62
|
// User-defined custom directories (#239)
|
|
49
63
|
const customDirs = loadCustomDirs();
|
|
@@ -64,3 +78,51 @@ export function initWorkspace(): string {
|
|
|
64
78
|
log.info("workspace", "ready", { workspacePath });
|
|
65
79
|
return workspacePath;
|
|
66
80
|
}
|
|
81
|
+
|
|
82
|
+
export const REQUIRED_GITIGNORE_LINES = [
|
|
83
|
+
"github/", // cloned repos have their own .git
|
|
84
|
+
".session-token", // bearer token regenerated each startup
|
|
85
|
+
".server-port", // runtime port published for the LLM wiki-write hook
|
|
86
|
+
] as const;
|
|
87
|
+
|
|
88
|
+
const FRESH_GITIGNORE = [
|
|
89
|
+
"# Cloned repositories have their own .git — don't nest",
|
|
90
|
+
"github/",
|
|
91
|
+
"",
|
|
92
|
+
"# Auth token (regenerated each startup)",
|
|
93
|
+
".session-token",
|
|
94
|
+
"",
|
|
95
|
+
"# Bound port published at startup for the wiki-write hook",
|
|
96
|
+
".server-port",
|
|
97
|
+
"",
|
|
98
|
+
].join("\n");
|
|
99
|
+
|
|
100
|
+
/** Decide what `.gitignore` should look like given its current
|
|
101
|
+
* content. Returns null when no rewrite is needed.
|
|
102
|
+
* - `null` existing → fresh template
|
|
103
|
+
* - existing missing required lines → existing + missing lines
|
|
104
|
+
* appended (preserves user customisation)
|
|
105
|
+
* - existing with all required lines → null (no change) */
|
|
106
|
+
export function nextGitignoreContent(existing: string | null): string | null {
|
|
107
|
+
if (existing === null) return FRESH_GITIGNORE;
|
|
108
|
+
// Treat the file as line-oriented; ignore comments and blanks for
|
|
109
|
+
// the comparison so a user's annotated copy still counts as
|
|
110
|
+
// having the line.
|
|
111
|
+
const present = new Set(
|
|
112
|
+
existing
|
|
113
|
+
.split(/\r?\n/)
|
|
114
|
+
.map((raw) => raw.trim())
|
|
115
|
+
.filter((line) => line.length > 0 && !line.startsWith("#")),
|
|
116
|
+
);
|
|
117
|
+
const missing = REQUIRED_GITIGNORE_LINES.filter((line) => !present.has(line));
|
|
118
|
+
if (missing.length === 0) return null;
|
|
119
|
+
const trailingNewline = existing.endsWith("\n") ? existing : `${existing}\n`;
|
|
120
|
+
return `${trailingNewline}${missing.join("\n")}\n`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ensureWorkspaceGitignore(): void {
|
|
124
|
+
const existing = readWorkspaceTextSync(".gitignore");
|
|
125
|
+
const next = nextGitignoreContent(existing);
|
|
126
|
+
if (next === null) return;
|
|
127
|
+
writeWorkspaceTextSync(".gitignore", next);
|
|
128
|
+
}
|
package/src/App.vue
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
:gemini-available="geminiAvailable"
|
|
10
10
|
:title-style="debugTitleStyle"
|
|
11
11
|
@test-query="(q) => sendMessage(q)"
|
|
12
|
-
@notification-navigate="handleNotificationNavigate"
|
|
13
12
|
@open-settings="showSettings = true"
|
|
14
13
|
@home="handleHomeClick"
|
|
15
14
|
/>
|
|
@@ -77,6 +76,8 @@
|
|
|
77
76
|
:roles="roles"
|
|
78
77
|
:error-message="historyError"
|
|
79
78
|
@load-session="handleSessionSelect"
|
|
79
|
+
@toggle-bookmark="(id, bookmarked) => setBookmark(id, bookmarked)"
|
|
80
|
+
@delete-session="(id) => deleteSessionFromHistory(id)"
|
|
80
81
|
/>
|
|
81
82
|
<SessionHistoryExpandButton :model-value="sidePanelExpanded" @update:model-value="(value: boolean) => (sidePanelExpanded = value)" />
|
|
82
83
|
</div>
|
|
@@ -123,7 +124,7 @@
|
|
|
123
124
|
v-model="userInput"
|
|
124
125
|
v-model:pasted-file="pastedFile"
|
|
125
126
|
:is-running="activeSessionRunning"
|
|
126
|
-
:queries="
|
|
127
|
+
:queries="sessionRoleQueries"
|
|
127
128
|
@send="sendMessage()"
|
|
128
129
|
@suggestion-send="(q) => sendMessage(q)"
|
|
129
130
|
/>
|
|
@@ -163,16 +164,51 @@
|
|
|
163
164
|
@update:layout-mode="setLayoutMode"
|
|
164
165
|
@toggle-right-sidebar="toggleRightSidebar"
|
|
165
166
|
/>
|
|
166
|
-
<!-- Distinct pages
|
|
167
|
+
<!-- Distinct pages. Plugin-owned views (Todo / Calendar /
|
|
168
|
+
Automations / Wiki / Skills) call `useRuntime()` from
|
|
169
|
+
`gui-chat-protocol/vue` inside their composables — that
|
|
170
|
+
throws unless mounted under `<PluginScopedRoot>`. The
|
|
171
|
+
plugin registry's `wrapWithScope` wraps the chat-mounted
|
|
172
|
+
variants; standalone routes are wrapped here against the
|
|
173
|
+
same `pkg-name + endpoints` pair so the `useRuntime()`
|
|
174
|
+
call resolves. -->
|
|
167
175
|
<FilesView v-else-if="currentPage === 'files'" :refresh-token="filesRefreshToken" @load-session="handleSessionSelect" />
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
<
|
|
172
|
-
|
|
176
|
+
<PluginScopedRoot v-else-if="currentPage === 'todos'" pkg-name="@mulmoclaude/todo-plugin">
|
|
177
|
+
<TodoExplorer />
|
|
178
|
+
</PluginScopedRoot>
|
|
179
|
+
<PluginScopedRoot v-else-if="currentPage === 'calendar'" pkg-name="scheduler" :endpoints="API_ROUTES.scheduler">
|
|
180
|
+
<CalendarView />
|
|
181
|
+
</PluginScopedRoot>
|
|
182
|
+
<PluginScopedRoot v-else-if="currentPage === 'automations'" pkg-name="scheduler" :endpoints="API_ROUTES.scheduler">
|
|
183
|
+
<AutomationsView />
|
|
184
|
+
</PluginScopedRoot>
|
|
185
|
+
<PluginScopedRoot v-else-if="currentPage === 'wiki'" pkg-name="wiki" :endpoints="API_ROUTES.wiki">
|
|
186
|
+
<WikiView />
|
|
187
|
+
</PluginScopedRoot>
|
|
188
|
+
<PluginScopedRoot v-else-if="currentPage === 'skills'" pkg-name="skills" :endpoints="API_ROUTES.skills">
|
|
189
|
+
<SkillsView />
|
|
190
|
+
</PluginScopedRoot>
|
|
173
191
|
<RolesView v-else-if="currentPage === 'roles'" />
|
|
174
192
|
<SourcesView v-else-if="currentPage === 'sources'" />
|
|
175
193
|
<NewsView v-else-if="currentPage === 'news'" />
|
|
194
|
+
<!-- Debug page (encore plan PR 1 follow-up). The View ships
|
|
195
|
+
inside the @mulmoclaude/debug-plugin runtime package; we
|
|
196
|
+
look it up by tool name and render the registered
|
|
197
|
+
viewComponent — already wrapped in PluginScopedRoot by
|
|
198
|
+
the runtime loader, so no extra scope wrapper here.
|
|
199
|
+
|
|
200
|
+
Literal English fallback below is intentional: the debug
|
|
201
|
+
surface is dev-only chrome behind `VITE_DEV_MODE=1`, so
|
|
202
|
+
we keep its strings out of the 8-locale i18n bundle.
|
|
203
|
+
Same policy applies to the launcher button (see
|
|
204
|
+
PluginLauncher.vue's `literalLabel`/`literalTitle`) and
|
|
205
|
+
to the page itself (debug-plugin/src/View.vue). -->
|
|
206
|
+
<component :is="debugViewComponent" v-else-if="currentPage === 'debug' && debugViewComponent" />
|
|
207
|
+
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -- debug page is dev-only chrome behind VITE_DEV_MODE=1; we deliberately keep its strings out of the 8-locale i18n bundle (see policy comment above). -->
|
|
208
|
+
<div v-else-if="currentPage === 'debug'" class="h-full flex items-center justify-center text-sm text-gray-500">
|
|
209
|
+
Debug plugin is not loaded. Make sure @mulmoclaude/debug-plugin is built and registered as a preset.
|
|
210
|
+
</div>
|
|
211
|
+
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
|
|
176
212
|
</div>
|
|
177
213
|
|
|
178
214
|
<!-- Bottom bar (Stack chat only — plugin views have no
|
|
@@ -190,7 +226,7 @@
|
|
|
190
226
|
v-model="userInput"
|
|
191
227
|
v-model:pasted-file="pastedFile"
|
|
192
228
|
:is-running="activeSessionRunning"
|
|
193
|
-
:queries="
|
|
229
|
+
:queries="sessionRoleQueries"
|
|
194
230
|
@send="sendMessage()"
|
|
195
231
|
@suggestion-send="(q) => sendMessage(q)"
|
|
196
232
|
/>
|
|
@@ -219,7 +255,6 @@
|
|
|
219
255
|
@update:open="showSettings = $event"
|
|
220
256
|
@ask-gemini="handleAskGemini"
|
|
221
257
|
/>
|
|
222
|
-
<NotificationToast />
|
|
223
258
|
</div>
|
|
224
259
|
</template>
|
|
225
260
|
|
|
@@ -227,8 +262,6 @@
|
|
|
227
262
|
import { ref, computed, watch, nextTick, onMounted, reactive } from "vue";
|
|
228
263
|
import { useI18n } from "vue-i18n";
|
|
229
264
|
import { v4 as uuidv4 } from "uuid";
|
|
230
|
-
|
|
231
|
-
const { t } = useI18n();
|
|
232
265
|
import { getPlugin } from "./tools";
|
|
233
266
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
234
267
|
import RightSidebar from "./components/RightSidebar.vue";
|
|
@@ -249,24 +282,22 @@ import AutomationsView from "./plugins/scheduler/AutomationsView.vue";
|
|
|
249
282
|
import WikiView from "./plugins/wiki/View.vue";
|
|
250
283
|
import { buildWikiRouteParams } from "./plugins/wiki/route";
|
|
251
284
|
import SkillsView from "./plugins/manageSkills/View.vue";
|
|
252
|
-
import RolesView from "./
|
|
285
|
+
import RolesView from "./components/RolesView.vue";
|
|
253
286
|
import SourcesView from "./components/SourcesView.vue";
|
|
254
287
|
import NewsView from "./components/NewsView.vue";
|
|
288
|
+
import PluginScopedRoot from "./components/PluginScopedRoot.vue";
|
|
255
289
|
import SettingsModal from "./components/SettingsModal.vue";
|
|
256
|
-
import NotificationToast from "./components/NotificationToast.vue";
|
|
257
|
-
import type { NotificationAction } from "./types/notification";
|
|
258
290
|
import { PAGE_ROUTES, type PageRouteName } from "./router";
|
|
259
291
|
import type { SseEvent } from "./types/sse";
|
|
260
|
-
import {
|
|
292
|
+
import type { SessionEntry, ActiveSession } from "./types/session";
|
|
261
293
|
import { EVENT_TYPES } from "./types/events";
|
|
262
|
-
import { extractImageData } from "./utils/tools/result";
|
|
263
294
|
import { buildAgentRequestBody, postAgentRun } from "./utils/agent/request";
|
|
295
|
+
import { resolvePastedAttachment } from "./utils/agent/pastedAttachment";
|
|
264
296
|
import { applyAgentEvent, type AgentEventContext } from "./utils/agent/eventDispatch";
|
|
265
297
|
import { pushErrorMessage, beginUserTurn, updateResult } from "./utils/session/sessionHelpers";
|
|
266
298
|
import { roleName, roleIcon } from "./utils/role/icon";
|
|
267
299
|
import { createEmptySession } from "./utils/session/sessionFactory";
|
|
268
300
|
import { buildLoadedSession, parseSessionEntries } from "./utils/session/sessionEntries";
|
|
269
|
-
import { resolveNotificationTarget } from "./utils/notification/dispatch";
|
|
270
301
|
import { usePendingCalls } from "./composables/usePendingCalls";
|
|
271
302
|
import { useRunElapsed } from "./composables/useRunElapsed";
|
|
272
303
|
import { useKeyNavigation } from "./composables/useKeyNavigation";
|
|
@@ -276,6 +307,7 @@ import { useViewLayout } from "./composables/useViewLayout";
|
|
|
276
307
|
import { useSessionSync } from "./composables/useSessionSync";
|
|
277
308
|
import { useSessionDerived } from "./composables/useSessionDerived";
|
|
278
309
|
import { useFaviconState } from "./composables/useFaviconState";
|
|
310
|
+
import { useGlobalImageErrorRepair } from "./composables/useImageErrorRepair";
|
|
279
311
|
import { useMergedSessions } from "./composables/useMergedSessions";
|
|
280
312
|
import { useLayoutMode } from "./composables/useLayoutMode";
|
|
281
313
|
import { useSidePanelVisible } from "./composables/useSidePanelVisible";
|
|
@@ -283,6 +315,7 @@ import { useSelectedResult } from "./composables/useSelectedResult";
|
|
|
283
315
|
import { useMcpTools } from "./composables/useMcpTools";
|
|
284
316
|
import { useRoles } from "./composables/useRoles";
|
|
285
317
|
import { useCurrentRole } from "./composables/useCurrentRole";
|
|
318
|
+
import { useTranslatedQueries } from "./composables/useTranslatedQueries";
|
|
286
319
|
import { BUILTIN_ROLE_IDS, type Role } from "./config/roles";
|
|
287
320
|
import { usePubSub } from "./composables/usePubSub";
|
|
288
321
|
import { sessionChannel } from "./config/pubsubChannels";
|
|
@@ -297,6 +330,8 @@ import { apiGet } from "./utils/api";
|
|
|
297
330
|
import { API_ROUTES } from "./config/apiRoutes";
|
|
298
331
|
import { classifyWorkspacePath } from "./utils/path/workspaceLinkRouter";
|
|
299
332
|
|
|
333
|
+
const { t, locale } = useI18n();
|
|
334
|
+
|
|
300
335
|
// --- Per-session state ---
|
|
301
336
|
// Declared early so that pub/sub callbacks and function declarations
|
|
302
337
|
// below can reference them without forward-reference ambiguity.
|
|
@@ -340,17 +375,6 @@ function navigateToSession(sessionId: string, replace = false): void {
|
|
|
340
375
|
});
|
|
341
376
|
}
|
|
342
377
|
|
|
343
|
-
function handleNotificationNavigate(action: NotificationAction): void {
|
|
344
|
-
const target = resolveNotificationTarget(action);
|
|
345
|
-
if (!target) return;
|
|
346
|
-
// No special-casing for chat targets: PR #774 moved the role
|
|
347
|
-
// selector's state into the `useCurrentRole` singleton, so the
|
|
348
|
-
// role no longer needs to be pinned via `?role=` on every
|
|
349
|
-
// session-navigate. router.push(target) keeps the user's
|
|
350
|
-
// current role choice across the navigation automatically.
|
|
351
|
-
router.push(target).catch(() => {});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
378
|
// External URL changes (back/forward button, typed URL) → update ref.
|
|
355
379
|
// If the session isn't in memory, load it from the server.
|
|
356
380
|
watch(
|
|
@@ -384,11 +408,17 @@ const userInput = ref("");
|
|
|
384
408
|
const pastedFile = ref<PastedFile | null>(null);
|
|
385
409
|
const activePane = ref<"sidebar" | "main">("sidebar");
|
|
386
410
|
|
|
387
|
-
const { sessions, historyError, fetchSessions } = useSessionHistory();
|
|
411
|
+
const { sessions, historyError, fetchSessions, setBookmark, deleteSession: deleteSessionFromHistory } = useSessionHistory();
|
|
388
412
|
const { markSessionRead } = useSessionSync({
|
|
389
413
|
sessionMap,
|
|
390
414
|
currentSessionId,
|
|
391
415
|
fetchSessions,
|
|
416
|
+
// Another tab hard-deleted the chat we're currently viewing. The
|
|
417
|
+
// sessionMap eviction has already cleared the in-memory state; the
|
|
418
|
+
// URL still points at the dead id. Mirror the URL→404 fallback on
|
|
419
|
+
// line ~366 by spinning up a fresh session so the user lands on a
|
|
420
|
+
// working /chat instead of a blank pane.
|
|
421
|
+
onCurrentSessionDeleted: () => createNewSession(),
|
|
392
422
|
});
|
|
393
423
|
const { geminiAvailable, sandboxEnabled, cpuLoadRatio, fetchHealth } = useHealth();
|
|
394
424
|
|
|
@@ -429,6 +459,12 @@ const sessionRole = computed<Role>(() => {
|
|
|
429
459
|
return roles.value[0];
|
|
430
460
|
});
|
|
431
461
|
|
|
462
|
+
// Translated suggested-query strings for the active session's role.
|
|
463
|
+
// Falls back to the role's English source until /api/translation
|
|
464
|
+
// returns; subsequent role swaps hit the in-memory cache.
|
|
465
|
+
const currentLocale = computed(() => String(locale.value));
|
|
466
|
+
const { queries: sessionRoleQueries } = useTranslatedQueries(sessionRole, currentLocale);
|
|
467
|
+
|
|
432
468
|
const { mergedSessions, tabSessions } = useMergedSessions({
|
|
433
469
|
sessionMap,
|
|
434
470
|
sessions,
|
|
@@ -446,6 +482,11 @@ const { mergedSessions, tabSessions } = useMergedSessions({
|
|
|
446
482
|
// very same tick as `isRunning` flips, without waiting for the next
|
|
447
483
|
// /api/sessions refetch.
|
|
448
484
|
useFaviconState({ isRunning, sessions: mergedSessions, sessionsUnreadCount: unreadCount, cpuLoadRatio });
|
|
485
|
+
useGlobalImageErrorRepair();
|
|
486
|
+
// Boot-time plugin META aggregator collisions surface as notifier
|
|
487
|
+
// entries from the server side; the notifier engine's persistent
|
|
488
|
+
// `active.json` covers the late-mount case (PR 4 of feat-encore),
|
|
489
|
+
// so no client-side catch-up fetch is required here.
|
|
449
490
|
|
|
450
491
|
const sessionSidebarRef = ref<{ root: HTMLDivElement | null } | null>(null);
|
|
451
492
|
const canvasRef = ref<HTMLDivElement | null>(null);
|
|
@@ -476,7 +517,7 @@ function setSidePanelVisibleAndCollapse(value: boolean): void {
|
|
|
476
517
|
// full-width views.
|
|
477
518
|
const isChatPage = computed(() => route.name === PAGE_ROUTES.chat);
|
|
478
519
|
const currentPage = computed<PageRouteName | null>(() => {
|
|
479
|
-
const name = route
|
|
520
|
+
const { name } = route;
|
|
480
521
|
return typeof name === "string" && isPageRouteName(name) ? name : null;
|
|
481
522
|
});
|
|
482
523
|
|
|
@@ -561,6 +602,12 @@ const { elapsedMs: runElapsedMs, teardown: teardownRunElapsed } = useRunElapsed(
|
|
|
561
602
|
|
|
562
603
|
const selectedResult = computed(() => toolResults.value.find((result) => result.uuid === selectedResultUuid.value) ?? null);
|
|
563
604
|
|
|
605
|
+
// Debug-plugin View component, looked up by tool name. The plugin
|
|
606
|
+
// loader populates this asynchronously at boot — `runtimeRegistry` is
|
|
607
|
+
// reactive, so this computed re-evaluates when the load completes and
|
|
608
|
+
// the /debug branch in the template lights up without a refresh.
|
|
609
|
+
const debugViewComponent = computed(() => getPlugin("manageDebug")?.viewComponent ?? null);
|
|
610
|
+
|
|
564
611
|
// Centralised session-switch handler: subscribe to the current session's
|
|
565
612
|
// pub/sub channel so we receive real-time events even if the session is
|
|
566
613
|
// idle (another tab may start a run). Unsubscribe from idle sessions
|
|
@@ -579,9 +626,11 @@ watch(currentSessionId, (sessionId) => {
|
|
|
579
626
|
// event, leaving the session's busy indicator stuck on.
|
|
580
627
|
if (previousSessionId && previousSessionId !== sessionId) {
|
|
581
628
|
const prevSession = sessionMap.get(previousSessionId);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
629
|
+
if (prevSession !== undefined) {
|
|
630
|
+
const prevBusy = prevSession.isRunning || Object.keys(prevSession.pendingGenerations ?? {}).length > 0;
|
|
631
|
+
if (!prevBusy) {
|
|
632
|
+
unsubscribeSession(previousSessionId);
|
|
633
|
+
}
|
|
585
634
|
}
|
|
586
635
|
}
|
|
587
636
|
previousSessionId = sessionId;
|
|
@@ -647,7 +696,7 @@ function createNewSession(roleId?: string): ActiveSession {
|
|
|
647
696
|
navigateToSession(session.id, replace);
|
|
648
697
|
chatInputRef.value?.collapseSuggestions();
|
|
649
698
|
nextTick(() => focusChatInput());
|
|
650
|
-
return
|
|
699
|
+
return session;
|
|
651
700
|
}
|
|
652
701
|
|
|
653
702
|
function onRoleChange(roleId: string) {
|
|
@@ -748,7 +797,8 @@ async function refreshSessionTranscript(sessionId: string): Promise<void> {
|
|
|
748
797
|
if (!session) return;
|
|
749
798
|
const response = await apiGet<SessionEntry[]>(API_ROUTES.sessions.detail.replace(":id", encodeURIComponent(sessionId)));
|
|
750
799
|
if (!response.ok) return;
|
|
751
|
-
const
|
|
800
|
+
const summary = sessions.value.find((entry) => entry.id === sessionId);
|
|
801
|
+
const serverResults = parseSessionEntries(response.data, summary?.origin);
|
|
752
802
|
// Only patch if the server knows more than we do — avoids
|
|
753
803
|
// replacing a richer in-flight state with a stale snapshot when
|
|
754
804
|
// session_finished races with the last few events.
|
|
@@ -775,7 +825,8 @@ function buildAgentEventContext(session: ActiveSession): AgentEventContext {
|
|
|
775
825
|
|
|
776
826
|
function hasPendingGenerations(sessionId: string): boolean {
|
|
777
827
|
const live = sessionMap.get(sessionId);
|
|
778
|
-
|
|
828
|
+
if (live === undefined) return false;
|
|
829
|
+
return Object.keys(live.pendingGenerations).length > 0;
|
|
779
830
|
}
|
|
780
831
|
|
|
781
832
|
function handleSessionFinished(sessionId: string): void {
|
|
@@ -826,11 +877,35 @@ async function sendMessage(text?: string) {
|
|
|
826
877
|
const fileSnapshot = pastedFile.value;
|
|
827
878
|
pastedFile.value = null;
|
|
828
879
|
|
|
880
|
+
// Pasted / dropped files are pre-uploaded to a workspace file so
|
|
881
|
+
// the server (and the LLM downstream) sees a relative path — never
|
|
882
|
+
// a data: URI. The path then rides on `attachments[]` as a path-only
|
|
883
|
+
// entry. On upload failure, restore both userInput and pastedFile so
|
|
884
|
+
// the user can retry without retyping.
|
|
885
|
+
let attachmentForRequest: string | undefined;
|
|
886
|
+
if (fileSnapshot) {
|
|
887
|
+
const resolved = await resolvePastedAttachment(fileSnapshot);
|
|
888
|
+
if (!resolved.ok) {
|
|
889
|
+
userInput.value = message;
|
|
890
|
+
pastedFile.value = fileSnapshot;
|
|
891
|
+
const recoverySession = sessionMap.get(currentSessionId.value);
|
|
892
|
+
if (recoverySession) pushErrorMessage(recoverySession, t("chatInput.attachImageFailed", { error: resolved.error }));
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
attachmentForRequest = resolved.value;
|
|
896
|
+
}
|
|
897
|
+
|
|
829
898
|
const session = sessionMap.get(currentSessionId.value);
|
|
830
899
|
if (!session) return;
|
|
831
900
|
|
|
832
|
-
|
|
833
|
-
|
|
901
|
+
// Only files the user explicitly attached this turn (paste / drop /
|
|
902
|
+
// file-picker) ride on the message. Do NOT auto-attach whatever
|
|
903
|
+
// image happens to be selected in the sidebar — selection moves to
|
|
904
|
+
// the latest generated image automatically, which would silently
|
|
905
|
+
// glue the previous picture onto every follow-up comment.
|
|
906
|
+
const attachmentPaths = attachmentForRequest ? [attachmentForRequest] : undefined;
|
|
907
|
+
|
|
908
|
+
beginUserTurn(session, message, attachmentPaths);
|
|
834
909
|
|
|
835
910
|
ensureSessionSubscription(session);
|
|
836
911
|
|
|
@@ -839,7 +914,7 @@ async function sendMessage(text?: string) {
|
|
|
839
914
|
message,
|
|
840
915
|
role: sessionRole.value,
|
|
841
916
|
chatSessionId: session.id,
|
|
842
|
-
|
|
917
|
+
attachmentPaths,
|
|
843
918
|
}),
|
|
844
919
|
);
|
|
845
920
|
if (!result.ok) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Re-export `gui-chat-protocol/vue` so a runtime-loaded plugin (#1110)
|
|
2
|
+
// can share the host's `useRuntime()` / `PLUGIN_RUNTIME_KEY` instances
|
|
3
|
+
// via importmap.
|
|
4
|
+
//
|
|
5
|
+
// Why a re-export at all: PLUGIN_RUNTIME_KEY is a `Symbol` created at
|
|
6
|
+
// module load time. If two copies of `gui-chat-protocol/vue` were
|
|
7
|
+
// loaded — one bundled into the host, another inlined in the plugin —
|
|
8
|
+
// they would each create their own Symbol, and `provide(K, runtime)`
|
|
9
|
+
// from the host would land on a different key than the plugin's
|
|
10
|
+
// `inject(K)` — `useRuntime()` would throw "called outside of
|
|
11
|
+
// <PluginScopedRoot>" even though the wrapper IS in the tree.
|
|
12
|
+
//
|
|
13
|
+
// The importmap entry in `index.html` (`gui-chat-protocol/vue` →
|
|
14
|
+
// `/src/_runtime/protocol-vue.ts`) makes the browser resolve the
|
|
15
|
+
// plugin's bare `import { useRuntime } from "gui-chat-protocol/vue"`
|
|
16
|
+
// to this same file the host already loaded — guaranteeing one
|
|
17
|
+
// shared module instance / one Symbol / one working inject path.
|
|
18
|
+
//
|
|
19
|
+
// `export *` mirrors the runtime-vue pattern next door: plugins compile
|
|
20
|
+
// against an evolving surface; we don't want to whitelist names here.
|
|
21
|
+
export * from "gui-chat-protocol/vue";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Re-export Vue's runtime so a runtime-loaded plugin (#1043 C-2) can
|
|
2
|
+
// share the host's Vue instance via importmap. The browser resolves a
|
|
3
|
+
// plugin's bare `import "vue"` (after the importmap declared in
|
|
4
|
+
// `index.html`) to this file. Vite transforms this module's own
|
|
5
|
+
// `from "vue"` to the same Vue chunk the host app uses, so host and
|
|
6
|
+
// runtime-loaded plugin component code end up referencing the same
|
|
7
|
+
// Vue singleton — reactivity flows across the boundary, and there is
|
|
8
|
+
// only one `createApp`, one Composition API, etc.
|
|
9
|
+
//
|
|
10
|
+
// Why `export *` and not a named re-export: plugin bundles compiled
|
|
11
|
+
// against `gui-chat-protocol/vue` import a wide and evolving surface
|
|
12
|
+
// of Vue (defineComponent, h, ref, computed, watch, …). Whitelisting
|
|
13
|
+
// would force this file to track Vue's API. `export *` gives the
|
|
14
|
+
// plugin the full surface it was compiled against.
|
|
15
|
+
//
|
|
16
|
+
// Build emit: registered as a Rollup input (`runtime-vue`) in
|
|
17
|
+
// `vite.config.ts` so it survives tree-shaking even though no
|
|
18
|
+
// static `import` references it. The build-only
|
|
19
|
+
// `runtimeImportmapBuildPlugin` rewrites the dev importmap URL
|
|
20
|
+
// (`/src/_runtime/vue.ts`) to the hashed asset filename in the
|
|
21
|
+
// emitted `index.html`.
|
|
22
|
+
export * from "vue";
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="border-t border-gray-200" @dragover.prevent @drop="onDropFile">
|
|
3
|
-
<SuggestionsPanel
|
|
3
|
+
<SuggestionsPanel
|
|
4
|
+
v-model:expanded="suggestionsExpanded"
|
|
5
|
+
:queries="queries"
|
|
6
|
+
:trigger-ref="suggestionsBtnRef"
|
|
7
|
+
@send="onSuggestionSend"
|
|
8
|
+
@edit="onSuggestionEdit"
|
|
9
|
+
/>
|
|
4
10
|
<div class="p-2">
|
|
5
11
|
<div v-if="fileError" class="mb-2 text-xs text-red-600 bg-red-50 border border-red-200 rounded px-3 py-1.5" data-testid="file-error">
|
|
6
12
|
{{ fileError }}
|
|
@@ -30,12 +36,12 @@
|
|
|
30
36
|
/>
|
|
31
37
|
<div class="flex flex-col gap-1">
|
|
32
38
|
<button
|
|
33
|
-
|
|
39
|
+
ref="suggestionsBtnRef"
|
|
34
40
|
data-testid="suggestions-btn"
|
|
35
41
|
class="rounded w-8 h-8 flex items-center justify-center"
|
|
36
42
|
:class="suggestionsExpanded ? 'text-blue-600 bg-blue-50' : 'text-gray-400 hover:text-gray-600'"
|
|
37
|
-
:title="t('suggestionsPanel.
|
|
38
|
-
:aria-label="t('suggestionsPanel.
|
|
43
|
+
:title="t('suggestionsPanel.tooltip')"
|
|
44
|
+
:aria-label="t('suggestionsPanel.tooltip')"
|
|
39
45
|
@click="suggestionsExpanded = !suggestionsExpanded"
|
|
40
46
|
>
|
|
41
47
|
<span class="material-icons text-base leading-none">lightbulb</span>
|
|
@@ -77,14 +83,11 @@ import { useI18n } from "vue-i18n";
|
|
|
77
83
|
import ChatAttachmentPreview from "./ChatAttachmentPreview.vue";
|
|
78
84
|
import SuggestionsPanel from "./SuggestionsPanel.vue";
|
|
79
85
|
import { useImeAwareEnter } from "../composables/useImeAwareEnter";
|
|
86
|
+
import type { PastedFile } from "../types/pastedFile";
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
export type { PastedFile };
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
dataUrl: string;
|
|
85
|
-
name: string;
|
|
86
|
-
mime: string;
|
|
87
|
-
}
|
|
90
|
+
const { t } = useI18n();
|
|
88
91
|
|
|
89
92
|
withDefaults(
|
|
90
93
|
defineProps<{
|
|
@@ -107,6 +110,7 @@ const textarea = ref<HTMLTextAreaElement | null>(null);
|
|
|
107
110
|
const fileError = ref<string | null>(null);
|
|
108
111
|
const fileInput = ref<HTMLInputElement | null>(null);
|
|
109
112
|
const suggestionsExpanded = ref(false);
|
|
113
|
+
const suggestionsBtnRef = ref<HTMLButtonElement | null>(null);
|
|
110
114
|
|
|
111
115
|
const MAX_ATTACH_BYTES = 30 * 1024 * 1024;
|
|
112
116
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Copies the current chat session to the clipboard as Markdown.
|
|
3
|
+
Lives in both <SessionSidebar> (single layout) and <StackView>
|
|
4
|
+
(stack layout) so the affordance is in the same visual slot
|
|
5
|
+
regardless of which layout the user is in.
|
|
6
|
+
|
|
7
|
+
Success feedback is local: the icon swaps to a check for ~1.5s.
|
|
8
|
+
No global toast composable yet (#TODO once one exists), and a
|
|
9
|
+
transient icon swap is enough since the user's hand is already
|
|
10
|
+
on the button. -->
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
14
|
+
:class="{ '!text-green-600': justCopied }"
|
|
15
|
+
data-testid="copy-chat-md"
|
|
16
|
+
:disabled="results.length === 0"
|
|
17
|
+
:title="justCopied ? t('sidebarHeader.copiedMarkdown') : t('sidebarHeader.copyMarkdown')"
|
|
18
|
+
:aria-label="justCopied ? t('sidebarHeader.copiedMarkdown') : t('sidebarHeader.copyMarkdown')"
|
|
19
|
+
@click="onCopy"
|
|
20
|
+
>
|
|
21
|
+
<span class="material-icons text-lg" aria-hidden="true">{{ justCopied ? "check" : "content_copy" }}</span>
|
|
22
|
+
</button>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { ref, onUnmounted } from "vue";
|
|
27
|
+
import { useI18n } from "vue-i18n";
|
|
28
|
+
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
29
|
+
import { exportChatToMarkdown } from "../utils/chat/exportMarkdown";
|
|
30
|
+
import { apiGet } from "../utils/api";
|
|
31
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
32
|
+
|
|
33
|
+
const COPIED_FEEDBACK_MS = 1500;
|
|
34
|
+
|
|
35
|
+
const { t } = useI18n();
|
|
36
|
+
|
|
37
|
+
const props = defineProps<{
|
|
38
|
+
results: ToolResultComplete[];
|
|
39
|
+
resultTimestamps: Map<string, number>;
|
|
40
|
+
sessionRoleName?: string;
|
|
41
|
+
}>();
|
|
42
|
+
|
|
43
|
+
const justCopied = ref(false);
|
|
44
|
+
let resetTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
45
|
+
|
|
46
|
+
async function readWorkspaceFile(path: string): Promise<string | null> {
|
|
47
|
+
const result = await apiGet<{ content?: string }>(API_ROUTES.files.content, { path });
|
|
48
|
+
if (!result.ok) return null;
|
|
49
|
+
return result.data.content ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function onCopy(): Promise<void> {
|
|
53
|
+
const markdown = await exportChatToMarkdown(props.results, {
|
|
54
|
+
sessionRoleName: props.sessionRoleName,
|
|
55
|
+
resultTimestamps: props.resultTimestamps,
|
|
56
|
+
readFile: readWorkspaceFile,
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
await navigator.clipboard.writeText(markdown);
|
|
60
|
+
justCopied.value = true;
|
|
61
|
+
if (resetTimeout !== null) clearTimeout(resetTimeout);
|
|
62
|
+
resetTimeout = setTimeout(() => {
|
|
63
|
+
justCopied.value = false;
|
|
64
|
+
resetTimeout = null;
|
|
65
|
+
}, COPIED_FEEDBACK_MS);
|
|
66
|
+
} catch {
|
|
67
|
+
// Clipboard write can reject when the document isn't focused or
|
|
68
|
+
// permission is denied. The missing visual confirmation is itself
|
|
69
|
+
// the user-facing signal; logging would only spam the console.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onUnmounted(() => {
|
|
74
|
+
if (resetTimeout !== null) clearTimeout(resetTimeout);
|
|
75
|
+
});
|
|
76
|
+
</script>
|