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
|
@@ -13,17 +13,31 @@ import { API_ROUTES } from "../config/apiRoutes";
|
|
|
13
13
|
import { apiFetchRaw } from "../utils/api";
|
|
14
14
|
import { errorMessage } from "../utils/errors";
|
|
15
15
|
|
|
16
|
+
export interface DownloadPdfOptions {
|
|
17
|
+
/** Workspace-relative source directory of the markdown (e.g.
|
|
18
|
+
* `"data/wiki/pages"` for Wiki pages). The server uses it to
|
|
19
|
+
* resolve relative `<img>` references to the right base path
|
|
20
|
+
* before inlining. Omit for the legacy `markdowns/` default. */
|
|
21
|
+
baseDir?: string;
|
|
22
|
+
/** When true, the server strips a leading YAML frontmatter envelope
|
|
23
|
+
* (`---\n…\n---\n`) before rendering, so the YAML header doesn't
|
|
24
|
+
* appear as plain text on page 1 of the PDF. Wiki pages set this.
|
|
25
|
+
* Other callers leave it false so a chat-generated document that
|
|
26
|
+
* literally starts with `---` is preserved. */
|
|
27
|
+
stripFrontmatter?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
export interface UsePdfDownloadHandle {
|
|
17
31
|
pdfDownloading: Ref<boolean>;
|
|
18
32
|
pdfError: Ref<string | null>;
|
|
19
|
-
downloadPdf: (markdown: string, filename: string) => Promise<void>;
|
|
33
|
+
downloadPdf: (markdown: string, filename: string, options?: DownloadPdfOptions) => Promise<void>;
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
export function usePdfDownload(): UsePdfDownloadHandle {
|
|
23
37
|
const pdfDownloading = ref(false);
|
|
24
38
|
const pdfError = ref<string | null>(null);
|
|
25
39
|
|
|
26
|
-
async function downloadPdf(markdown: string, filename: string): Promise<void> {
|
|
40
|
+
async function downloadPdf(markdown: string, filename: string, options: DownloadPdfOptions = {}): Promise<void> {
|
|
27
41
|
pdfError.value = null;
|
|
28
42
|
pdfDownloading.value = true;
|
|
29
43
|
let url: string | null = null;
|
|
@@ -33,7 +47,7 @@ export function usePdfDownload(): UsePdfDownloadHandle {
|
|
|
33
47
|
const response = await apiFetchRaw(API_ROUTES.pdf.markdown, {
|
|
34
48
|
method: "POST",
|
|
35
49
|
headers: { "Content-Type": "application/json" },
|
|
36
|
-
body: JSON.stringify({ markdown, filename }),
|
|
50
|
+
body: JSON.stringify({ markdown, filename, baseDir: options.baseDir, stripFrontmatter: options.stripFrontmatter }),
|
|
37
51
|
});
|
|
38
52
|
if (!response.ok) {
|
|
39
53
|
const errText = await response.text().catch(() => "");
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// every 50ms so the `pendingCalls` computed re-evaluates and any
|
|
4
|
-
// freshly-resolved call stays visible for at least PENDING_MIN_MS
|
|
5
|
-
// before disappearing. After the run ends, schedule one final tick
|
|
6
|
-
// so the computed clears the lingering rows.
|
|
1
|
+
// "Minimum visible duration" trick: tick every 50ms while running so a freshly-resolved call stays on-screen for
|
|
2
|
+
// PENDING_MIN_MS before clearing. One final tick after the run ends sweeps lingering rows out of the computed.
|
|
7
3
|
|
|
8
4
|
import { computed, ref, watch, type ComputedRef, type Ref } from "vue";
|
|
9
5
|
import type { ToolCallHistoryItem } from "../types/toolCallHistory";
|
|
@@ -17,17 +13,14 @@ interface UsePendingCallsOptions {
|
|
|
17
13
|
export function usePendingCalls(opts: UsePendingCallsOptions) {
|
|
18
14
|
const displayTick = ref(0);
|
|
19
15
|
let tickInterval: ReturnType<typeof setInterval> | null = null;
|
|
20
|
-
// Tracked so teardown can cancel the
|
|
21
|
-
// never mutate displayTick after the composable's owner unmounts.
|
|
16
|
+
// Tracked so teardown can cancel the trailing tick — never mutate displayTick after the owner unmounts.
|
|
22
17
|
let delayedTickTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
23
18
|
|
|
24
19
|
watch(
|
|
25
20
|
opts.isRunning,
|
|
26
21
|
(running) => {
|
|
27
22
|
if (running) {
|
|
28
|
-
// Guard against double-start
|
|
29
|
-
// running=true (e.g. immediate + a synchronous flip), don't
|
|
30
|
-
// stack a second interval.
|
|
23
|
+
// Guard against double-start (immediate + a synchronous flip would otherwise stack intervals).
|
|
31
24
|
if (tickInterval !== null) return;
|
|
32
25
|
tickInterval = setInterval(() => {
|
|
33
26
|
displayTick.value++;
|
|
@@ -35,9 +28,7 @@ export function usePendingCalls(opts: UsePendingCallsOptions) {
|
|
|
35
28
|
} else if (tickInterval !== null) {
|
|
36
29
|
clearInterval(tickInterval);
|
|
37
30
|
tickInterval = null;
|
|
38
|
-
//
|
|
39
|
-
// duration has elapsed. Cancel any previous pending one first
|
|
40
|
-
// so back-to-back start/stop runs do not stack timeouts.
|
|
31
|
+
// Cancel any previous trailing tick so back-to-back start/stop runs don't stack timeouts.
|
|
41
32
|
if (delayedTickTimeout !== null) clearTimeout(delayedTickTimeout);
|
|
42
33
|
delayedTickTimeout = setTimeout(() => {
|
|
43
34
|
displayTick.value++;
|
|
@@ -45,24 +36,15 @@ export function usePendingCalls(opts: UsePendingCallsOptions) {
|
|
|
45
36
|
}, PENDING_MIN_MS);
|
|
46
37
|
}
|
|
47
38
|
},
|
|
48
|
-
//
|
|
49
|
-
// flight (e.g. mounted mid-stream) starts ticking right away
|
|
50
|
-
// instead of waiting for the next isRunning flip.
|
|
39
|
+
// Immediate so a composable mounted mid-stream starts ticking right away instead of waiting for the next flip.
|
|
51
40
|
{ immediate: true },
|
|
52
41
|
);
|
|
53
42
|
|
|
54
43
|
const pendingCalls = computed(() => {
|
|
55
|
-
//
|
|
56
|
-
// dependency on it — that is how a freshly-resolved row stays
|
|
57
|
-
// visible for the minimum window. The `__` prefix tells ESLint
|
|
58
|
-
// (varsIgnorePattern: "^__") that the variable is intentionally
|
|
59
|
-
// unused.
|
|
44
|
+
// Reads displayTick purely to register a reactive dep — that's how rows linger for the minimum window.
|
|
60
45
|
const __tickDep = displayTick.value;
|
|
61
46
|
const now = Date.now();
|
|
62
|
-
//
|
|
63
|
-
// consumer doesn't need its own ticker for the per-tool badge —
|
|
64
|
-
// the 50ms re-evaluation here already drives the display
|
|
65
|
-
// (#731 PR2).
|
|
47
|
+
// #731 PR2: project to elapsedMs so the per-tool badge piggybacks on this 50ms ticker (no second ticker downstream).
|
|
66
48
|
return opts.toolCallHistory.value
|
|
67
49
|
.filter((entry) => __tickDep >= 0 && isCallStillPending(entry, now))
|
|
68
50
|
.map((entry) => ({
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// State and reset logic for the plugin error boundary mounted in
|
|
2
|
+
// `<PluginScopedRoot>`. Extracted from the SFC so the behaviour
|
|
3
|
+
// (error capture, details toggle, retry-remount key bump, error
|
|
4
|
+
// detail composition) can be unit-tested without a DOM — Codex
|
|
5
|
+
// review iter-1 #1147 flagged the absence of automated coverage
|
|
6
|
+
// for the boundary's wiring.
|
|
7
|
+
//
|
|
8
|
+
// The host component still owns `onErrorCaptured` registration —
|
|
9
|
+
// that hook must be called inside `setup()` and is not portable —
|
|
10
|
+
// but every observable side-effect of "an error was captured" lives
|
|
11
|
+
// here and is fully testable in isolation.
|
|
12
|
+
|
|
13
|
+
import { computed, ref, type ComputedRef, type Ref } from "vue";
|
|
14
|
+
|
|
15
|
+
export interface PluginErrorBoundary {
|
|
16
|
+
/** Captured error object, or `null` while the plugin renders
|
|
17
|
+
* normally. The host SFC reads this to switch to the fallback
|
|
18
|
+
* panel. */
|
|
19
|
+
readonly error: Readonly<Ref<Error | null>>;
|
|
20
|
+
/** Toggle for the "Show details" / "Hide details" disclosure. */
|
|
21
|
+
readonly showDetails: Ref<boolean>;
|
|
22
|
+
/** Bumped on every Retry. The SFC binds it as `<slot :key>` so
|
|
23
|
+
* Vue treats the slotted subtree as a brand-new component on
|
|
24
|
+
* each retry — transient bugs (stale refs, momentary endpoint
|
|
25
|
+
* outages) clear without a full page reload. */
|
|
26
|
+
readonly mountKey: Readonly<Ref<number>>;
|
|
27
|
+
/** Composed `<message>\n\n<stack>` text shown inside the
|
|
28
|
+
* details `<pre>`. Empty string when there's no error. */
|
|
29
|
+
readonly errorDetails: ComputedRef<string>;
|
|
30
|
+
/** Forward an unknown thrown value into the boundary's error
|
|
31
|
+
* state. Coerces non-Error throws to `new Error(String(err))`
|
|
32
|
+
* so `.stack` access stays type-safe in the template. Logs to
|
|
33
|
+
* the console with a `[plugin/<pkgName>]` prefix so devs can
|
|
34
|
+
* trace which plugin owned the crash. */
|
|
35
|
+
readonly captureError: (err: unknown) => void;
|
|
36
|
+
/** Clear the error and bump `mountKey`. */
|
|
37
|
+
readonly retry: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @param pkgName plugin package name; used as the console log
|
|
41
|
+
* prefix so the owning plugin is obvious in dev
|
|
42
|
+
* tools. */
|
|
43
|
+
export function usePluginErrorBoundary(pkgName: string): PluginErrorBoundary {
|
|
44
|
+
const error = ref<Error | null>(null);
|
|
45
|
+
const showDetails = ref(false);
|
|
46
|
+
const mountKey = ref(0);
|
|
47
|
+
|
|
48
|
+
const errorDetails = computed((): string => {
|
|
49
|
+
if (!error.value) return "";
|
|
50
|
+
const message = error.value.message || String(error.value);
|
|
51
|
+
const stack = error.value.stack ?? "";
|
|
52
|
+
return stack ? `${message}\n\n${stack}` : message;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function captureError(err: unknown): void {
|
|
56
|
+
const captured = err instanceof Error ? err : new Error(String(err));
|
|
57
|
+
console.error(`[plugin/${pkgName}] uncaught error`, captured);
|
|
58
|
+
error.value = captured;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function retry(): void {
|
|
62
|
+
error.value = null;
|
|
63
|
+
showDetails.value = false;
|
|
64
|
+
mountKey.value += 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { error, showDetails, mountKey, errorDetails, captureError, retry };
|
|
68
|
+
}
|
|
@@ -8,15 +8,7 @@ interface PubSubMessage {
|
|
|
8
8
|
type Callback = (data: unknown) => void;
|
|
9
9
|
type Unsubscribe = () => void;
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
// state machine. One multiplexed connection; channels map to
|
|
13
|
-
// socket.io rooms via `subscribe` / `unsubscribe` events.
|
|
14
|
-
//
|
|
15
|
-
// Reconnect / backoff / heartbeat are all handled by socket.io,
|
|
16
|
-
// so there's no reconnectTimer / reconnectDelay here anymore. On
|
|
17
|
-
// reconnect, `connect` fires again and we re-send every
|
|
18
|
-
// subscription the client still cares about.
|
|
19
|
-
|
|
11
|
+
// On reconnect we re-emit every live subscription so the rooms list survives the bounce.
|
|
20
12
|
let socket: Socket | null = null;
|
|
21
13
|
|
|
22
14
|
const listeners = new Map<string, Set<Callback>>();
|
|
@@ -32,9 +24,7 @@ function connect(): Socket {
|
|
|
32
24
|
|
|
33
25
|
const sock = io({
|
|
34
26
|
path: "/ws/pubsub",
|
|
35
|
-
//
|
|
36
|
-
// the server refuses it, so don't negotiate it here either —
|
|
37
|
-
// fail fast if the WS upgrade doesn't go through.
|
|
27
|
+
// Server refuses long-polling fallback, so fail fast here too if the WS upgrade doesn't go through.
|
|
38
28
|
transports: ["websocket"],
|
|
39
29
|
});
|
|
40
30
|
|
|
@@ -60,14 +50,16 @@ function maybeDisconnect(): void {
|
|
|
60
50
|
|
|
61
51
|
export function usePubSub() {
|
|
62
52
|
function subscribe(channel: string, callback: Callback): Unsubscribe {
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
let entry = listeners.get(channel);
|
|
54
|
+
if (!entry) {
|
|
55
|
+
entry = new Set();
|
|
56
|
+
listeners.set(channel, entry);
|
|
57
|
+
}
|
|
58
|
+
entry.add(callback);
|
|
65
59
|
|
|
66
60
|
const sock = connect();
|
|
67
61
|
if (sock.connected) sock.emit("subscribe", channel);
|
|
68
|
-
// If not yet connected, the "connect" handler replays every
|
|
69
|
-
// listener's subscription, so newly-added channels are
|
|
70
|
-
// covered without extra bookkeeping.
|
|
62
|
+
// If not yet connected, the "connect" handler replays every subscription — no extra bookkeeping needed.
|
|
71
63
|
|
|
72
64
|
return () => {
|
|
73
65
|
const cbs = listeners.get(channel);
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// rendered string ("12s" / "1m 23s") moves visibly. When the run
|
|
4
|
-
// ends, `elapsedMs` flips back to null and the timer is cleared.
|
|
5
|
-
//
|
|
6
|
-
// Separated from `usePendingCalls` (which ticks every 50ms for the
|
|
7
|
-
// minimum-visible-duration trick) — the run-elapsed display only
|
|
8
|
-
// needs second-granularity, and the consumer renders one badge per
|
|
9
|
-
// run rather than one per pending row, so a tighter tick would just
|
|
10
|
-
// burn re-renders.
|
|
11
|
-
//
|
|
12
|
-
// Why a watcher + setInterval rather than a `requestAnimationFrame`
|
|
13
|
-
// driven computed: tab-throttled rAF freezes when the tab is in the
|
|
14
|
-
// background, and the user expects the elapsed counter to keep
|
|
15
|
-
// running across tab switches.
|
|
1
|
+
// setInterval, not rAF: tab-throttled rAF freezes in background tabs and the user expects elapsed to keep ticking
|
|
2
|
+
// across tab switches. Second-granularity is enough — the consumer renders one badge per run, not per pending row.
|
|
16
3
|
|
|
17
4
|
import { computed, ref, watch, type ComputedRef, type Ref, type WatchStopHandle } from "vue";
|
|
18
5
|
|
|
@@ -35,9 +22,7 @@ export function useRunElapsed(opts: UseRunElapsedOptions): {
|
|
|
35
22
|
opts.isRunning,
|
|
36
23
|
(running) => {
|
|
37
24
|
if (running) {
|
|
38
|
-
// Guard against double-start
|
|
39
|
-
// running=true (e.g. immediate + a synchronous flip), don't
|
|
40
|
-
// stack a second interval.
|
|
25
|
+
// Guard against double-start (immediate + synchronous flip would otherwise stack intervals).
|
|
41
26
|
if (interval !== null) return;
|
|
42
27
|
startedAt.value = Date.now();
|
|
43
28
|
now.value = startedAt.value;
|
|
@@ -52,8 +37,7 @@ export function useRunElapsed(opts: UseRunElapsedOptions): {
|
|
|
52
37
|
}
|
|
53
38
|
startedAt.value = null;
|
|
54
39
|
},
|
|
55
|
-
//
|
|
56
|
-
// flight (mounted mid-stream) starts ticking right away.
|
|
40
|
+
// Immediate: a composable mounted mid-stream starts ticking right away.
|
|
57
41
|
{ immediate: true },
|
|
58
42
|
);
|
|
59
43
|
|
|
@@ -63,8 +47,7 @@ export function useRunElapsed(opts: UseRunElapsedOptions): {
|
|
|
63
47
|
});
|
|
64
48
|
|
|
65
49
|
function teardown(): void {
|
|
66
|
-
// Stop the watcher first — otherwise an isRunning flip after
|
|
67
|
-
// teardown would recreate the interval (Codex iter-1 #798).
|
|
50
|
+
// Stop the watcher first — otherwise an isRunning flip after teardown recreates the interval (#798 Codex iter-1).
|
|
68
51
|
if (stopWatch !== null) {
|
|
69
52
|
stopWatch();
|
|
70
53
|
stopWatch = null;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
// Lazy
|
|
2
|
-
//
|
|
3
|
-
// The lock popup consumes this to show which host credentials are
|
|
4
|
-
// attached to the Docker sandbox. Deliberately lazy: the popup is
|
|
5
|
-
// hidden most of the time, and env-var changes only take effect on
|
|
6
|
-
// server restart anyway, so a page-lifetime cache populated on first
|
|
7
|
-
// open is enough.
|
|
8
|
-
//
|
|
9
|
-
// Paired with `useHealth` (which loads `sandboxEnabled` at bootstrap)
|
|
10
|
-
// — when the sandbox is disabled this composable is never called.
|
|
1
|
+
// #329. Lazy because env-var changes only take effect on server restart, so a page-lifetime cache is enough.
|
|
11
2
|
|
|
12
3
|
import { ref, type Ref } from "vue";
|
|
13
4
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
@@ -33,12 +24,8 @@ function isSandboxStatus(raw: RawResponse): raw is {
|
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
export interface UseSandboxStatusHandle {
|
|
36
|
-
|
|
37
|
-
* / fetch failed. UI renders a placeholder in all three cases. */
|
|
27
|
+
// null = not yet loaded / sandbox disabled / fetch failed (UI renders a placeholder for all three).
|
|
38
28
|
status: Ref<SandboxStatus | null>;
|
|
39
|
-
/** One-shot loader. Safe to call repeatedly — short-circuits once a
|
|
40
|
-
* non-null value is cached. Designed to be triggered from a
|
|
41
|
-
* `watch(() => props.open, …)` on the popup. */
|
|
42
29
|
ensureLoaded: () => Promise<void>;
|
|
43
30
|
}
|
|
44
31
|
|
|
@@ -51,14 +38,11 @@ export function useSandboxStatus(): UseSandboxStatusHandle {
|
|
|
51
38
|
loaded = true;
|
|
52
39
|
const result = await apiGet<RawResponse>(API_ROUTES.sandbox);
|
|
53
40
|
if (!result.ok) {
|
|
54
|
-
//
|
|
55
|
-
// line. Allow a retry on next open by flipping `loaded` back.
|
|
41
|
+
// Allow retry on next open — `status` stays null so the popup shows "state unavailable".
|
|
56
42
|
loaded = false;
|
|
57
43
|
return;
|
|
58
44
|
}
|
|
59
|
-
// Server returns `{}`
|
|
60
|
-
// The popup shouldn't call us in that case, but double-guard here
|
|
61
|
-
// so a stale render doesn't blow up on shape validation.
|
|
45
|
+
// Server returns `{}` when the sandbox is disabled — popup shouldn't call us then, but double-guard.
|
|
62
46
|
if (!isSandboxStatus(result.data)) return;
|
|
63
47
|
status.value = result.data;
|
|
64
48
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Computed properties derived from sessionMap + sessions list.
|
|
2
|
-
// Extracted from App.vue to reduce the component's reactive surface.
|
|
3
|
-
|
|
4
1
|
import { computed, type Ref } from "vue";
|
|
5
2
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
6
3
|
import type { ActiveSession, SessionSummary } from "../types/session";
|
|
@@ -14,17 +11,16 @@ export function useSessionDerived(opts: { sessionMap: Map<string, ActiveSession>
|
|
|
14
11
|
|
|
15
12
|
const toolResults = computed<ToolResultComplete[]>(() => activeSession.value?.toolResults ?? []);
|
|
16
13
|
|
|
14
|
+
// `sidebarResults` is the canonical "what the user sees and can
|
|
15
|
+
// navigate through" list — keyboard nav (useKeyNavigation), the
|
|
16
|
+
// sidebar render, and StackView all consume it.
|
|
17
17
|
const sidebarResults = computed(() => deduplicateResults(toolResults.value));
|
|
18
18
|
|
|
19
19
|
const currentSummary = computed(() => sessions.value.find((summary) => summary.id === currentSessionId.value));
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// Used for consumers that must stay true across page navigation:
|
|
25
|
-
// favicon spinner and the FilesView refresh watcher (which would
|
|
26
|
-
// otherwise fire before a background run actually finishes, because
|
|
27
|
-
// leaving /chat drops activeSession to undefined).
|
|
21
|
+
// OR of in-memory map (pub/sub-fast) + server summaries (covers un-hydrated sessions). Must stay true across page
|
|
22
|
+
// nav so favicon + FilesView refresh-watcher don't fire before a background run finishes (leaving /chat drops
|
|
23
|
+
// activeSession to undefined).
|
|
28
24
|
const isRunning = computed(() => {
|
|
29
25
|
for (const session of sessionMap.values()) {
|
|
30
26
|
if (session.isRunning) return true;
|
|
@@ -33,11 +29,7 @@ export function useSessionDerived(opts: { sessionMap: Map<string, ActiveSession>
|
|
|
33
29
|
return sessions.value.some((summary) => summary.isRunning);
|
|
34
30
|
});
|
|
35
31
|
|
|
36
|
-
//
|
|
37
|
-
// UX touchpoints that should react per-session — ChatInput disable,
|
|
38
|
-
// sendMessage guard, chat-list auto-scroll, pending-call row tick —
|
|
39
|
-
// so a background run in session B doesn't disable the composer
|
|
40
|
-
// while the user is actively chatting in session A.
|
|
32
|
+
// Per-session: a background run in session B must not disable session A's composer or block its auto-scroll.
|
|
41
33
|
const activeSessionRunning = computed(() => {
|
|
42
34
|
const active = activeSession.value;
|
|
43
35
|
const pending = active ? Object.keys(active.pendingGenerations).length > 0 : false;
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// Owns the `sessions` list (what the server knows about) plus the
|
|
4
|
-
// fetch helper. The view's open/closed state is now URL-backed (see
|
|
5
|
-
// plans/done/feat-history-url-route.md) — callers watch `route.name` and
|
|
6
|
-
// invoke `fetchSessions()` on route enter rather than going through
|
|
7
|
-
// an in-memory toggle flag.
|
|
8
|
-
//
|
|
9
|
-
// Since #205, `fetchSessions()` sends the server's last-issued
|
|
10
|
-
// cursor back as `?since=<cursor>` so the server can reply with
|
|
11
|
-
// only the rows that changed. The first call has no cursor (full
|
|
12
|
-
// fetch); subsequent calls receive a diff that we merge into the
|
|
13
|
-
// existing cache via `applySessionDiff`.
|
|
1
|
+
// #205: send the server's last cursor as ?since=<cursor> so the server replies with a diff. First call has no cursor.
|
|
14
2
|
|
|
15
|
-
import { ref, type Ref } from "vue";
|
|
3
|
+
import { getCurrentScope, onScopeDispose, ref, type Ref } from "vue";
|
|
16
4
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
5
|
+
import { PUBSUB_CHANNELS, type SessionsChannelPayload } from "../config/pubsubChannels";
|
|
17
6
|
import type { SessionSummary } from "../types/session";
|
|
18
|
-
import { apiGet } from "../utils/api";
|
|
7
|
+
import { apiDelete, apiGet, apiPost } from "../utils/api";
|
|
19
8
|
import { applySessionDiff } from "../utils/session/mergeSessions";
|
|
9
|
+
import { usePubSub } from "./usePubSub";
|
|
20
10
|
|
|
21
11
|
interface SessionsResponse {
|
|
22
12
|
sessions: SessionSummary[];
|
|
@@ -24,20 +14,25 @@ interface SessionsResponse {
|
|
|
24
14
|
deletedIds: string[];
|
|
25
15
|
}
|
|
26
16
|
|
|
27
|
-
|
|
17
|
+
function readDeletedIds(payload: unknown): string[] {
|
|
18
|
+
if (!payload || typeof payload !== "object") return [];
|
|
19
|
+
const ids = (payload as SessionsChannelPayload).deletedIds;
|
|
20
|
+
return Array.isArray(ids) ? ids.filter((entry): entry is string => typeof entry === "string") : [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseSessionHistory {
|
|
28
24
|
sessions: Ref<SessionSummary[]>;
|
|
29
25
|
historyError: Ref<string | null>;
|
|
30
26
|
fetchSessions: () => Promise<SessionSummary[]>;
|
|
31
|
-
|
|
27
|
+
setBookmark: (sessionId: string, bookmarked: boolean) => Promise<boolean>;
|
|
28
|
+
deleteSession: (sessionId: string) => Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useSessionHistory(): UseSessionHistory {
|
|
32
32
|
const sessions = ref<SessionSummary[]>([]);
|
|
33
|
-
//
|
|
34
|
-
// sessions list rather than wiping it — a panel that goes blank
|
|
35
|
-
// the moment the network hiccups is worse UX than one that shows
|
|
36
|
-
// "⚠ using cached list" with the last-known good entries.
|
|
33
|
+
// Held alongside the stale list, not in place of it — a blank panel on a network blip is worse UX than "⚠ cached".
|
|
37
34
|
const historyError = ref<string | null>(null);
|
|
38
|
-
//
|
|
39
|
-
// Tab-scoped — issue #205 calls out cross-tab sharing via
|
|
40
|
-
// localStorage as out of scope.
|
|
35
|
+
// Tab-scoped; #205 explicitly leaves cross-tab sharing via localStorage out of scope.
|
|
41
36
|
let cursor: string | null = null;
|
|
42
37
|
|
|
43
38
|
async function fetchSessions(): Promise<SessionSummary[]> {
|
|
@@ -46,26 +41,72 @@ export function useSessionHistory(): {
|
|
|
46
41
|
const result = await apiGet<SessionsResponse>(API_ROUTES.sessions.list, query);
|
|
47
42
|
if (!result.ok) {
|
|
48
43
|
historyError.value = result.error;
|
|
49
|
-
//
|
|
50
|
-
// whatever list was last known to work.
|
|
44
|
+
// Preserve sessions.value so callers keep showing the last-known-good list.
|
|
51
45
|
return sessions.value;
|
|
52
46
|
}
|
|
53
47
|
historyError.value = null;
|
|
54
48
|
const body = result.data;
|
|
55
49
|
if (cursor === null) {
|
|
56
|
-
// First call in this composable instance — server returned the
|
|
57
|
-
// full list; seed the cache directly.
|
|
58
50
|
sessions.value = body.sessions;
|
|
59
51
|
} else {
|
|
60
52
|
sessions.value = applySessionDiff(sessions.value, body.sessions, body.deletedIds);
|
|
61
53
|
}
|
|
62
|
-
cursor = body
|
|
54
|
+
({ cursor } = body);
|
|
63
55
|
return sessions.value;
|
|
64
56
|
}
|
|
65
57
|
|
|
58
|
+
async function setBookmark(sessionId: string, bookmarked: boolean): Promise<boolean> {
|
|
59
|
+
const path = API_ROUTES.sessions.bookmark.replace(":id", encodeURIComponent(sessionId));
|
|
60
|
+
const result = await apiPost<{ ok: boolean }>(path, { bookmarked });
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
historyError.value = result.error;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// Optimistic local update so the green-icon flip is immediate;
|
|
66
|
+
// the pub/sub round-trip will reaffirm via the cursor diff (meta
|
|
67
|
+
// mtime feeds into changeMs) and also reach other tabs.
|
|
68
|
+
sessions.value = sessions.value.map((session) => (session.id === sessionId ? { ...session, isBookmarked: bookmarked } : session));
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function deleteSession(sessionId: string): Promise<boolean> {
|
|
73
|
+
const path = API_ROUTES.sessions.detail.replace(":id", encodeURIComponent(sessionId));
|
|
74
|
+
const result = await apiDelete<{ ok: boolean }>(path);
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
historyError.value = result.error;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Don't update locally — the server publishes `deletedIds` on the
|
|
80
|
+
// sessions channel and the subscriber below removes the row in
|
|
81
|
+
// every tab (including this one) the same way. One code path, no
|
|
82
|
+
// race between the optimistic write and the broadcast.
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Cross-tab cache pruning: cursor diffs don't carry deletions
|
|
87
|
+
// (deletedIds is always [] in the REST response — see #205 comments
|
|
88
|
+
// in routes/sessions.ts), so we rely on the channel payload.
|
|
89
|
+
//
|
|
90
|
+
// Gated on getCurrentScope() so unit tests that instantiate the
|
|
91
|
+
// composable outside a Vue setup don't open a real socket.io
|
|
92
|
+
// connection (which would keep node's event loop alive and hang
|
|
93
|
+
// the test process).
|
|
94
|
+
if (getCurrentScope()) {
|
|
95
|
+
const { subscribe } = usePubSub();
|
|
96
|
+
const unsubscribe = subscribe(PUBSUB_CHANNELS.sessions, (data) => {
|
|
97
|
+
const ids = readDeletedIds(data);
|
|
98
|
+
if (ids.length === 0) return;
|
|
99
|
+
const drop = new Set(ids);
|
|
100
|
+
sessions.value = sessions.value.filter((session) => !drop.has(session.id));
|
|
101
|
+
});
|
|
102
|
+
if (typeof unsubscribe === "function") onScopeDispose(unsubscribe);
|
|
103
|
+
}
|
|
104
|
+
|
|
66
105
|
return {
|
|
67
106
|
sessions,
|
|
68
107
|
historyError,
|
|
69
108
|
fetchSessions,
|
|
109
|
+
setBookmark,
|
|
110
|
+
deleteSession,
|
|
70
111
|
};
|
|
71
112
|
}
|
|
@@ -7,16 +7,27 @@ import { onScopeDispose } from "vue";
|
|
|
7
7
|
import type { Ref } from "vue";
|
|
8
8
|
import type { ActiveSession, SessionSummary } from "../types/session";
|
|
9
9
|
import { usePubSub } from "./usePubSub";
|
|
10
|
-
import { PUBSUB_CHANNELS } from "../config/pubsubChannels";
|
|
10
|
+
import { PUBSUB_CHANNELS, type SessionsChannelPayload } from "../config/pubsubChannels";
|
|
11
11
|
import { apiPost } from "../utils/api";
|
|
12
12
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
13
13
|
|
|
14
|
+
function readDeletedIds(payload: unknown): string[] {
|
|
15
|
+
if (!payload || typeof payload !== "object") return [];
|
|
16
|
+
const ids = (payload as SessionsChannelPayload).deletedIds;
|
|
17
|
+
return Array.isArray(ids) ? ids.filter((entry): entry is string => typeof entry === "string") : [];
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
export function useSessionSync(opts: {
|
|
15
21
|
sessionMap: Map<string, ActiveSession>;
|
|
16
22
|
currentSessionId: Ref<string>;
|
|
17
23
|
fetchSessions: () => Promise<SessionSummary[]>;
|
|
24
|
+
/** Called when the session the user is currently viewing has been
|
|
25
|
+
* hard-deleted (typically from another tab). The host owns the
|
|
26
|
+
* recovery action — usually navigate to a fresh session so the
|
|
27
|
+
* blank chat view doesn't linger on a dead URL. */
|
|
28
|
+
onCurrentSessionDeleted?: () => void;
|
|
18
29
|
}) {
|
|
19
|
-
const { sessionMap, currentSessionId, fetchSessions } = opts;
|
|
30
|
+
const { sessionMap, currentSessionId, fetchSessions, onCurrentSessionDeleted } = opts;
|
|
20
31
|
const { subscribe } = usePubSub();
|
|
21
32
|
|
|
22
33
|
async function refreshSessionStates(): Promise<void> {
|
|
@@ -48,7 +59,18 @@ export function useSessionSync(opts: {
|
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
const unsub = subscribe(PUBSUB_CHANNELS.sessions, () => {
|
|
62
|
+
const unsub = subscribe(PUBSUB_CHANNELS.sessions, (data) => {
|
|
63
|
+
// Hard-deleted sessions need to leave sessionMap immediately —
|
|
64
|
+
// refreshSessionStates only updates entries it still finds in the
|
|
65
|
+
// server response, so a deleted live session would otherwise
|
|
66
|
+
// linger in mergedSessions until the tab reloads.
|
|
67
|
+
const deletedIds = readDeletedIds(data);
|
|
68
|
+
let currentWasDeleted = false;
|
|
69
|
+
for (const deletedId of deletedIds) {
|
|
70
|
+
sessionMap.delete(deletedId);
|
|
71
|
+
if (deletedId === currentSessionId.value) currentWasDeleted = true;
|
|
72
|
+
}
|
|
73
|
+
if (currentWasDeleted) onCurrentSessionDeleted?.();
|
|
52
74
|
void refreshSessionStates();
|
|
53
75
|
});
|
|
54
76
|
if (typeof unsub === "function") onScopeDispose(unsub);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readonly, ref, type Ref, type DeepReadonly } from "vue";
|
|
2
|
+
import { apiGet } from "../utils/api";
|
|
3
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
4
|
+
|
|
5
|
+
export interface SkillSummary {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
source: "user" | "project";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Module-level shared state across consumers. Failed fetch keeps the previous list (no visual wipe on a blip) and
|
|
12
|
+
// surfaces the message via `error` — the Skills tab renders that as a banner so stale vs current is distinguishable.
|
|
13
|
+
const skills = ref<SkillSummary[]>([]);
|
|
14
|
+
const error = ref<string | null>(null);
|
|
15
|
+
let bootstrapped = false;
|
|
16
|
+
let inflight: Promise<void> | null = null;
|
|
17
|
+
|
|
18
|
+
async function refresh(): Promise<void> {
|
|
19
|
+
if (inflight) return inflight;
|
|
20
|
+
inflight = (async () => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await apiGet<{ skills: SkillSummary[] }>(API_ROUTES.skills.list.url);
|
|
23
|
+
if (result.ok && Array.isArray(result.data.skills)) {
|
|
24
|
+
skills.value = result.data.skills;
|
|
25
|
+
error.value = null;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Leave `skills` untouched (stale > wiped); surface on `error` AND console.warn — silent failures here had
|
|
29
|
+
// historically made "Skills tab won't refresh" hard to diagnose without breakpoints.
|
|
30
|
+
const message = !result.ok ? result.error || "Failed to load skills" : "Skills response missing `skills` array";
|
|
31
|
+
error.value = message;
|
|
32
|
+
console.warn("[useSkillsList] refresh failed:", message);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Runtime throw must not become an unhandled rejection — the bootstrap call site is `void refresh()`.
|
|
35
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
36
|
+
error.value = message;
|
|
37
|
+
console.warn("[useSkillsList] refresh threw:", err);
|
|
38
|
+
} finally {
|
|
39
|
+
inflight = null;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
return inflight;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useSkillsList(): {
|
|
46
|
+
skills: DeepReadonly<Ref<SkillSummary[]>>;
|
|
47
|
+
error: DeepReadonly<Ref<string | null>>;
|
|
48
|
+
refresh: () => Promise<void>;
|
|
49
|
+
} {
|
|
50
|
+
if (!bootstrapped) {
|
|
51
|
+
bootstrapped = true;
|
|
52
|
+
void refresh();
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
skills: readonly(skills),
|
|
56
|
+
error: readonly(error),
|
|
57
|
+
refresh,
|
|
58
|
+
};
|
|
59
|
+
}
|