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
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { Router, Request, Response } from "express";
|
|
2
2
|
import { realpathSync } from "fs";
|
|
3
3
|
import { readdir, stat } from "fs/promises";
|
|
4
|
-
import { readTextSafe } from "../../utils/files/safe.js";
|
|
4
|
+
import { readTextSafe, resolveWithinRoot } from "../../utils/files/safe.js";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { workspacePath } from "../../workspace/workspace.js";
|
|
7
7
|
import { WORKSPACE_PATHS } from "../../workspace/paths.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import {
|
|
9
|
+
readSessionMeta as readSessionMetaIO,
|
|
10
|
+
readSessionJsonl,
|
|
11
|
+
sessionJsonlAbsPath,
|
|
12
|
+
sessionMetaAbsPath,
|
|
13
|
+
updateIsBookmarked,
|
|
14
|
+
deleteSessionFiles,
|
|
15
|
+
} from "../../utils/files/session-io.js";
|
|
16
|
+
import { readManifest, removeSessionFromIndex } from "../../workspace/chat-index/indexer.js";
|
|
11
17
|
import type { ChatIndexEntry } from "../../workspace/chat-index/types.js";
|
|
12
|
-
import { markRead, getSession } from "../../events/session-store/index.js";
|
|
18
|
+
import { markRead, getSession, evictSession, publishSessionsChanged } from "../../events/session-store/index.js";
|
|
13
19
|
import { notFound } from "../../utils/httpError.js";
|
|
14
20
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
15
21
|
import { EVENT_TYPES } from "../../../src/types/events.js";
|
|
@@ -25,6 +31,7 @@ interface SessionMeta {
|
|
|
25
31
|
startedAt: string;
|
|
26
32
|
firstUserMessage?: string;
|
|
27
33
|
hasUnread?: boolean;
|
|
34
|
+
isBookmarked?: boolean;
|
|
28
35
|
origin?: SessionOrigin;
|
|
29
36
|
}
|
|
30
37
|
|
|
@@ -66,6 +73,9 @@ export interface SessionSummary {
|
|
|
66
73
|
keywords?: string[];
|
|
67
74
|
// Where this session originated (#486). Missing = "human".
|
|
68
75
|
origin?: SessionOrigin;
|
|
76
|
+
// User-toggled bookmark — surfaced in the history panel as a
|
|
77
|
+
// dedicated filter chip and a green role-icon tint.
|
|
78
|
+
isBookmarked?: boolean;
|
|
69
79
|
// Live state from the in-memory session store. Absent when the
|
|
70
80
|
// session has no active entry in the store (i.e. idle / historical).
|
|
71
81
|
isRunning?: boolean;
|
|
@@ -97,73 +107,98 @@ const router = Router();
|
|
|
97
107
|
// SESSIONS_LIST_WINDOW_DAYS to override (0 = no cutoff).
|
|
98
108
|
const WINDOW_MS = env.sessionsListWindowDays * ONE_DAY_MS;
|
|
99
109
|
|
|
110
|
+
interface SessionRowContext {
|
|
111
|
+
chatDir: string;
|
|
112
|
+
cutoff: number;
|
|
113
|
+
indexById: Map<string, ChatIndexEntry>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface SessionRow {
|
|
117
|
+
summary: SessionSummary;
|
|
118
|
+
changeMs: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Build a SessionSummary from the gathered inputs. Conditional spreads
|
|
122
|
+
// honour the server tsconfig's `exactOptionalPropertyTypes`; that's
|
|
123
|
+
// why each optional field is set with `if (… !== undefined)` rather
|
|
124
|
+
// than spread into the object literal directly.
|
|
125
|
+
function buildSessionSummary(
|
|
126
|
+
sessionId: string,
|
|
127
|
+
meta: SessionMeta,
|
|
128
|
+
indexEntry: ChatIndexEntry | undefined,
|
|
129
|
+
fileStat: { mtimeMs: number },
|
|
130
|
+
live: ReturnType<typeof getSession>,
|
|
131
|
+
): SessionSummary {
|
|
132
|
+
// Prefer AI title → meta.firstUserMessage → empty.
|
|
133
|
+
const preview = indexEntry?.title ?? meta.firstUserMessage ?? "";
|
|
134
|
+
const summary: SessionSummary = {
|
|
135
|
+
id: sessionId,
|
|
136
|
+
roleId: meta.roleId,
|
|
137
|
+
startedAt: meta.startedAt,
|
|
138
|
+
updatedAt: new Date(fileStat.mtimeMs).toISOString(),
|
|
139
|
+
preview,
|
|
140
|
+
hasUnread: live?.hasUnread ?? meta.hasUnread ?? false,
|
|
141
|
+
};
|
|
142
|
+
if (meta.origin) summary.origin = meta.origin;
|
|
143
|
+
if (meta.isBookmarked) summary.isBookmarked = true;
|
|
144
|
+
if (indexEntry?.summary !== undefined) summary.summary = indexEntry.summary;
|
|
145
|
+
if (indexEntry?.keywords !== undefined) summary.keywords = indexEntry.keywords;
|
|
146
|
+
if (live) {
|
|
147
|
+
// Background generations (image/audio/movie) keep the session
|
|
148
|
+
// "busy" even when the agent turn has ended, so the sidebar
|
|
149
|
+
// indicator stays lit across view navigation.
|
|
150
|
+
summary.isRunning = live.isRunning || Object.keys(live.pendingGenerations).length > 0;
|
|
151
|
+
summary.statusMessage = live.statusMessage;
|
|
152
|
+
}
|
|
153
|
+
return summary;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Load one session's row, or null when the session should be skipped
|
|
157
|
+
// (cutoff window, missing meta, any I/O error). The `metaMtimeMs`
|
|
158
|
+
// fallback lets a brand-new session contribute 0 to its changeMs
|
|
159
|
+
// instead of crashing the whole listing.
|
|
160
|
+
async function loadSessionRow(sessionId: string, ctx: SessionRowContext): Promise<SessionRow | null> {
|
|
161
|
+
try {
|
|
162
|
+
// stat only — no readFile on .jsonl content
|
|
163
|
+
const fileStat = await stat(sessionJsonlAbsPath(sessionId));
|
|
164
|
+
if (ctx.cutoff > 0 && fileStat.mtimeMs < ctx.cutoff) return null;
|
|
165
|
+
|
|
166
|
+
const meta = await readSessionMeta(ctx.chatDir, sessionId);
|
|
167
|
+
if (!meta) return null;
|
|
168
|
+
|
|
169
|
+
// The meta sidecar bumps its mtime on hasUnread / origin writes —
|
|
170
|
+
// feed it into changeMs so cursor-based refetches pick up drains
|
|
171
|
+
// of background generations (which only touch meta, not the
|
|
172
|
+
// jsonl).
|
|
173
|
+
const metaMtimeMs = await stat(sessionMetaAbsPath(sessionId))
|
|
174
|
+
.then((stats) => stats.mtimeMs)
|
|
175
|
+
.catch(() => 0);
|
|
176
|
+
|
|
177
|
+
const indexEntry = ctx.indexById.get(sessionId);
|
|
178
|
+
const live = getSession(sessionId);
|
|
179
|
+
return {
|
|
180
|
+
summary: buildSessionSummary(sessionId, meta, indexEntry, fileStat, live),
|
|
181
|
+
changeMs: sessionChangeMs(fileStat.mtimeMs, indexEntry?.indexedAt, metaMtimeMs),
|
|
182
|
+
};
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
100
188
|
// Read the full session list off disk. Each row carries its
|
|
101
189
|
// `changeMs` — the later of the jsonl mtime and the chat-index
|
|
102
190
|
// `indexedAt` — so the handler can filter against `?since=` and
|
|
103
191
|
// compute the new cursor without re-statting anything.
|
|
104
|
-
export async function loadAllSessions(): Promise<
|
|
192
|
+
export async function loadAllSessions(): Promise<SessionRow[]> {
|
|
105
193
|
const chatDir = WORKSPACE_PATHS.chat;
|
|
106
194
|
const manifest = await readManifest(workspacePath);
|
|
107
195
|
const indexById = new Map<string, ChatIndexEntry>(manifest.entries.map((entry) => [entry.id, entry]));
|
|
108
196
|
const cutoff = WINDOW_MS > 0 ? Date.now() - WINDOW_MS : 0;
|
|
197
|
+
const ctx: SessionRowContext = { chatDir, cutoff, indexById };
|
|
109
198
|
|
|
110
199
|
const files = (await readdir(chatDir)).filter((fileName) => fileName.endsWith(".jsonl"));
|
|
111
|
-
const rows = await Promise.all(
|
|
112
|
-
|
|
113
|
-
const sessionId = file.replace(".jsonl", "");
|
|
114
|
-
try {
|
|
115
|
-
// stat only — no readFile on .jsonl content
|
|
116
|
-
const fileStat = await stat(sessionJsonlAbsPath(sessionId));
|
|
117
|
-
if (cutoff > 0 && fileStat.mtimeMs < cutoff) return null;
|
|
118
|
-
|
|
119
|
-
const meta = await readSessionMeta(chatDir, sessionId);
|
|
120
|
-
if (!meta) return null;
|
|
121
|
-
|
|
122
|
-
// The meta sidecar bumps its mtime on hasUnread / origin
|
|
123
|
-
// writes — feed it into changeMs so cursor-based refetches
|
|
124
|
-
// pick up drains of background generations (which only touch
|
|
125
|
-
// meta, not the jsonl). Missing stat (brand-new session
|
|
126
|
-
// before its first meta write) contributes 0.
|
|
127
|
-
const metaMtimeMs = await stat(sessionMetaAbsPath(sessionId))
|
|
128
|
-
.then((stats) => stats.mtimeMs)
|
|
129
|
-
.catch(() => 0);
|
|
130
|
-
|
|
131
|
-
const indexEntry = indexById.get(sessionId);
|
|
132
|
-
// Prefer AI title → meta.firstUserMessage → empty.
|
|
133
|
-
// `summary` and `keywords` are spread conditionally
|
|
134
|
-
// to respect the server tsconfig's
|
|
135
|
-
// exactOptionalPropertyTypes.
|
|
136
|
-
const preview = indexEntry?.title ?? meta.firstUserMessage ?? "";
|
|
137
|
-
|
|
138
|
-
const live = getSession(sessionId);
|
|
139
|
-
const summary: SessionSummary = {
|
|
140
|
-
id: sessionId,
|
|
141
|
-
roleId: meta.roleId,
|
|
142
|
-
startedAt: meta.startedAt,
|
|
143
|
-
updatedAt: new Date(fileStat.mtimeMs).toISOString(),
|
|
144
|
-
preview,
|
|
145
|
-
hasUnread: live?.hasUnread ?? meta.hasUnread ?? false,
|
|
146
|
-
};
|
|
147
|
-
if (meta.origin) summary.origin = meta.origin;
|
|
148
|
-
if (indexEntry?.summary !== undefined) summary.summary = indexEntry.summary;
|
|
149
|
-
if (indexEntry?.keywords !== undefined) summary.keywords = indexEntry.keywords;
|
|
150
|
-
if (live) {
|
|
151
|
-
// Background generations (image/audio/movie) keep the session
|
|
152
|
-
// "busy" even when the agent turn has ended, so the sidebar
|
|
153
|
-
// indicator stays lit across view navigation.
|
|
154
|
-
summary.isRunning = live.isRunning || Object.keys(live.pendingGenerations).length > 0;
|
|
155
|
-
summary.statusMessage = live.statusMessage;
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
summary,
|
|
159
|
-
changeMs: sessionChangeMs(fileStat.mtimeMs, indexEntry?.indexedAt, metaMtimeMs),
|
|
160
|
-
};
|
|
161
|
-
} catch {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
return rows.filter((row): row is { summary: SessionSummary; changeMs: number } => row !== null);
|
|
200
|
+
const rows = await Promise.all(files.map((file) => loadSessionRow(file.replace(".jsonl", ""), ctx)));
|
|
201
|
+
return rows.filter((row): row is SessionRow => row !== null);
|
|
167
202
|
}
|
|
168
203
|
|
|
169
204
|
router.get(API_ROUTES.sessions.list, async (req: Request<object, SessionsResponse, object, SessionsQuery>, res: Response<SessionsResponse>) => {
|
|
@@ -211,6 +246,79 @@ interface SessionErrorResponse {
|
|
|
211
246
|
error: string;
|
|
212
247
|
}
|
|
213
248
|
|
|
249
|
+
// Narrow type predicate for the presentMulmoScript tool-result shape
|
|
250
|
+
// the enrichment helper inspects. Returning `entry is …` lets the
|
|
251
|
+
// caller drop the redundant nested checks once the predicate passes.
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
+
function isPresentMulmoScriptToolResult(entry: any): entry is {
|
|
254
|
+
source: "tool";
|
|
255
|
+
type: string;
|
|
256
|
+
result: { toolName: "presentMulmoScript"; data: { filePath: string } & Record<string, unknown> };
|
|
257
|
+
} & Record<string, unknown> {
|
|
258
|
+
return (
|
|
259
|
+
entry?.source === "tool" &&
|
|
260
|
+
entry?.type === EVENT_TYPES.toolResult &&
|
|
261
|
+
entry?.result?.toolName === "presentMulmoScript" &&
|
|
262
|
+
typeof entry?.result?.data?.filePath === "string"
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Re-read the MulmoScript JSON pointed at by `entry.result.data.filePath`
|
|
267
|
+
// and merge it into a copy of the entry. Returns the original entry on
|
|
268
|
+
// any failure (missing file, traversal escape, parse error, absolute
|
|
269
|
+
// path) so the detail route never breaks because of a single rotted
|
|
270
|
+
// link. Path-traversal guard is realpath-based — see
|
|
271
|
+
// resolveWithinRoot for why.
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
273
|
+
async function enrichWithMulmoScript(entry: any): Promise<any> {
|
|
274
|
+
try {
|
|
275
|
+
const storiesDir = path.resolve(WORKSPACE_PATHS.stories);
|
|
276
|
+
let storiesReal: string;
|
|
277
|
+
try {
|
|
278
|
+
storiesReal = realpathSync(storiesDir);
|
|
279
|
+
} catch {
|
|
280
|
+
return entry;
|
|
281
|
+
}
|
|
282
|
+
const scriptRelPath: string = entry.result.data.filePath;
|
|
283
|
+
if (path.isAbsolute(scriptRelPath)) return entry;
|
|
284
|
+
const relFromStories = scriptRelPath.startsWith("stories/") ? scriptRelPath.slice("stories/".length) : scriptRelPath;
|
|
285
|
+
const scriptPath = resolveWithinRoot(storiesReal, relFromStories);
|
|
286
|
+
if (!scriptPath) return entry;
|
|
287
|
+
const scriptJson = (await readTextSafe(scriptPath)) ?? "";
|
|
288
|
+
return {
|
|
289
|
+
...entry,
|
|
290
|
+
result: {
|
|
291
|
+
...entry.result,
|
|
292
|
+
data: {
|
|
293
|
+
...entry.result.data,
|
|
294
|
+
script: JSON.parse(scriptJson),
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
} catch {
|
|
299
|
+
return entry;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Parse one JSONL line into a session entry, applying the legacy-meta
|
|
304
|
+
// skip and the presentMulmoScript enrichment in one place. Returns
|
|
305
|
+
// null when the line should not appear in the response (parse error
|
|
306
|
+
// or legacy meta).
|
|
307
|
+
async function parseSessionEntry(line: string): Promise<unknown> {
|
|
308
|
+
let entry: unknown;
|
|
309
|
+
try {
|
|
310
|
+
entry = JSON.parse(line);
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
// Skip legacy metadata entries now stored in .json
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
316
|
+
const typed = entry as any;
|
|
317
|
+
if (typed?.type === EVENT_TYPES.sessionMeta || typed?.type === EVENT_TYPES.claudeSessionId) return null;
|
|
318
|
+
if (isPresentMulmoScriptToolResult(typed)) return enrichWithMulmoScript(typed);
|
|
319
|
+
return typed;
|
|
320
|
+
}
|
|
321
|
+
|
|
214
322
|
router.get(API_ROUTES.sessions.detail, async (req: Request<SessionIdParams>, res: Response<unknown[] | SessionErrorResponse>) => {
|
|
215
323
|
const { id: sessionId } = req.params;
|
|
216
324
|
const chatDir = WORKSPACE_PATHS.chat;
|
|
@@ -223,65 +331,7 @@ router.get(API_ROUTES.sessions.detail, async (req: Request<SessionIdParams>, res
|
|
|
223
331
|
notFound(res, `Session ${sessionId} not found`);
|
|
224
332
|
return;
|
|
225
333
|
}
|
|
226
|
-
const entries = (
|
|
227
|
-
await Promise.all(
|
|
228
|
-
content
|
|
229
|
-
.split("\n")
|
|
230
|
-
.filter(Boolean)
|
|
231
|
-
.map(async (line) => {
|
|
232
|
-
try {
|
|
233
|
-
const entry = JSON.parse(line);
|
|
234
|
-
// Skip legacy metadata entries now stored in .json
|
|
235
|
-
if (entry.type === EVENT_TYPES.sessionMeta || entry.type === EVENT_TYPES.claudeSessionId) return null;
|
|
236
|
-
// For presentMulmoScript results, re-read the script from disk
|
|
237
|
-
if (
|
|
238
|
-
entry.source === "tool" &&
|
|
239
|
-
entry.type === EVENT_TYPES.toolResult &&
|
|
240
|
-
entry.result?.toolName === "presentMulmoScript" &&
|
|
241
|
-
entry.result?.data?.filePath
|
|
242
|
-
) {
|
|
243
|
-
try {
|
|
244
|
-
// Realpath-based traversal check defeats symlink
|
|
245
|
-
// escapes — see resolveWithinRoot in utils/fs.ts.
|
|
246
|
-
// Resolve the stories dir's realpath so the
|
|
247
|
-
// boundary check works even when stories/ itself
|
|
248
|
-
// is a legitimate symlink to another disk.
|
|
249
|
-
const storiesDir = path.resolve(WORKSPACE_PATHS.stories);
|
|
250
|
-
let storiesReal: string;
|
|
251
|
-
try {
|
|
252
|
-
storiesReal = realpathSync(storiesDir);
|
|
253
|
-
} catch {
|
|
254
|
-
return entry;
|
|
255
|
-
}
|
|
256
|
-
const scriptRelPath: string = entry.result.data.filePath;
|
|
257
|
-
if (path.isAbsolute(scriptRelPath)) return entry;
|
|
258
|
-
// Strip optional "stories/" prefix so the
|
|
259
|
-
// remainder is relative to storiesReal.
|
|
260
|
-
const relFromStories = scriptRelPath.startsWith("stories/") ? scriptRelPath.slice("stories/".length) : scriptRelPath;
|
|
261
|
-
const scriptPath = resolveWithinRoot(storiesReal, relFromStories);
|
|
262
|
-
if (!scriptPath) return entry;
|
|
263
|
-
const scriptJson = (await readTextSafe(scriptPath)) ?? "";
|
|
264
|
-
return {
|
|
265
|
-
...entry,
|
|
266
|
-
result: {
|
|
267
|
-
...entry.result,
|
|
268
|
-
data: {
|
|
269
|
-
...entry.result.data,
|
|
270
|
-
script: JSON.parse(scriptJson),
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
} catch {
|
|
275
|
-
// file missing — return original entry
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return entry;
|
|
279
|
-
} catch {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
}),
|
|
283
|
-
)
|
|
284
|
-
).filter(Boolean);
|
|
334
|
+
const entries = (await Promise.all(content.split("\n").filter(Boolean).map(parseSessionEntry))).filter(Boolean);
|
|
285
335
|
// Prepend metadata as session_meta entry for the frontend
|
|
286
336
|
const result = meta ? [{ type: EVENT_TYPES.sessionMeta, ...meta }, ...entries] : entries;
|
|
287
337
|
log.info("sessions", "detail: ok", { sessionId, entries: result.length });
|
|
@@ -302,4 +352,67 @@ router.post(API_ROUTES.sessions.markRead, async (req: Request<SessionIdParams>,
|
|
|
302
352
|
res.json({ ok: true });
|
|
303
353
|
});
|
|
304
354
|
|
|
355
|
+
// Toggle the user-set bookmark flag on a session's meta sidecar.
|
|
356
|
+
router.post(
|
|
357
|
+
API_ROUTES.sessions.bookmark,
|
|
358
|
+
async (
|
|
359
|
+
req: Request<SessionIdParams, { ok: boolean } | SessionErrorResponse, { bookmarked: boolean }>,
|
|
360
|
+
res: Response<{ ok: boolean } | SessionErrorResponse>,
|
|
361
|
+
) => {
|
|
362
|
+
const { id: sessionId } = req.params;
|
|
363
|
+
const bookmarked = Boolean(req.body?.bookmarked);
|
|
364
|
+
log.info("sessions", "bookmark: start", { sessionId, bookmarked });
|
|
365
|
+
try {
|
|
366
|
+
await updateIsBookmarked(sessionId, bookmarked);
|
|
367
|
+
// Meta-mtime bumps on the write — cursor diff will pick up the
|
|
368
|
+
// change on the next refetch — but every other tab also needs
|
|
369
|
+
// to know to refetch right now.
|
|
370
|
+
publishSessionsChanged();
|
|
371
|
+
log.info("sessions", "bookmark: ok", { sessionId, bookmarked });
|
|
372
|
+
res.json({ ok: true });
|
|
373
|
+
} catch (err) {
|
|
374
|
+
log.error("sessions", "bookmark: threw", { sessionId, error: errorMessage(err) });
|
|
375
|
+
res.status(500).json({ error: "Failed to update bookmark" });
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
// Hard-delete a session: remove the jsonl, meta sidecar, AND the
|
|
381
|
+
// chat-index per-session file + manifest entry, then evict the
|
|
382
|
+
// in-memory store entry and broadcast `deletedIds` so every tab
|
|
383
|
+
// prunes its caches.
|
|
384
|
+
//
|
|
385
|
+
// Order matters:
|
|
386
|
+
// 1. Refuse if the session is currently running. The agent route
|
|
387
|
+
// (server/api/routes/agent.ts) writes via `appendSessionLine` /
|
|
388
|
+
// `endRun`; deleting the files out from under a live run would
|
|
389
|
+
// either resurrect them or corrupt mid-stream state. Caller
|
|
390
|
+
// should cancel first, then retry the delete.
|
|
391
|
+
// 2. Delete the on-disk artifacts (jsonl + meta + index entry +
|
|
392
|
+
// manifest prune). If any of these throw we abort BEFORE
|
|
393
|
+
// evicting / broadcasting, so clients don't see "session gone"
|
|
394
|
+
// while the file lingers.
|
|
395
|
+
// 3. Only after disk is clean do we evict from the store and fire
|
|
396
|
+
// `notifySessionsChanged({ deletedIds })`. Now the broadcast is
|
|
397
|
+
// a truthful statement.
|
|
398
|
+
router.delete(API_ROUTES.sessions.detail, async (req: Request<SessionIdParams>, res: Response<{ ok: boolean } | SessionErrorResponse>) => {
|
|
399
|
+
const { id: sessionId } = req.params;
|
|
400
|
+
log.info("sessions", "delete: start", { sessionId });
|
|
401
|
+
if (getSession(sessionId)?.isRunning) {
|
|
402
|
+
log.warn("sessions", "delete: refused — session running", { sessionId });
|
|
403
|
+
res.status(409).json({ error: "Session is running. Cancel the run before deleting." });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
await deleteSessionFiles(sessionId);
|
|
408
|
+
await removeSessionFromIndex(workspacePath, sessionId);
|
|
409
|
+
evictSession(sessionId);
|
|
410
|
+
log.info("sessions", "delete: ok", { sessionId });
|
|
411
|
+
res.json({ ok: true });
|
|
412
|
+
} catch (err) {
|
|
413
|
+
log.error("sessions", "delete: threw", { sessionId, error: errorMessage(err) });
|
|
414
|
+
res.status(500).json({ error: "Failed to delete session" });
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
305
418
|
export default router;
|
|
@@ -17,6 +17,7 @@ import { deleteProjectSkill, discoverSkills, saveProjectSkill, updateProjectSkil
|
|
|
17
17
|
import type { Skill, SkillSummary } from "../../workspace/skills/index.js";
|
|
18
18
|
import { workspacePath } from "../../workspace/workspace.js";
|
|
19
19
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
20
|
+
import { bindRoute } from "../../utils/router.js";
|
|
20
21
|
import { log } from "../../system/logger/index.js";
|
|
21
22
|
import { refreshScheduledSkills } from "../../workspace/skills/scheduler.js";
|
|
22
23
|
import { logBackgroundError } from "../../utils/logBackgroundError.js";
|
|
@@ -52,7 +53,7 @@ interface DeleteSkillResponse {
|
|
|
52
53
|
name: string;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
router
|
|
56
|
+
bindRoute(router, API_ROUTES.skills.list, async (_req: Request, res: Response<SkillsListResponse>) => {
|
|
56
57
|
const skills = await discoverSkills({ workspaceRoot: workspacePath });
|
|
57
58
|
log.info("skills", "list: ok", { count: skills.length });
|
|
58
59
|
res.json({
|
|
@@ -64,7 +65,7 @@ router.get(API_ROUTES.skills.list, async (_req: Request, res: Response<SkillsLis
|
|
|
64
65
|
});
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
router
|
|
68
|
+
bindRoute(router, API_ROUTES.skills.detail, async (req: Request<{ name: string }>, res: Response<SkillDetailResponse | ErrorResponse>) => {
|
|
68
69
|
log.info("skills", "detail: start", { name: req.params.name });
|
|
69
70
|
const skills = await discoverSkills({ workspaceRoot: workspacePath });
|
|
70
71
|
const skill = skills.find((candidate) => candidate.name === req.params.name);
|
|
@@ -76,7 +77,7 @@ router.get(API_ROUTES.skills.detail, async (req: Request<{ name: string }>, res:
|
|
|
76
77
|
res.json({ skill });
|
|
77
78
|
});
|
|
78
79
|
|
|
79
|
-
router
|
|
80
|
+
bindRoute(router, API_ROUTES.skills.create, async (req: Request<object, unknown, SaveSkillBody>, res: Response<SaveSkillResponse | ErrorResponse>) => {
|
|
80
81
|
const { name, description, body } = req.body ?? {};
|
|
81
82
|
log.info("skills", "create: start", { name: typeof name === "string" ? name : undefined });
|
|
82
83
|
if (typeof name !== "string") {
|
|
@@ -135,54 +136,58 @@ interface UpdateSkillResponse {
|
|
|
135
136
|
path: string;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
|
|
139
|
+
bindRoute(
|
|
140
|
+
router,
|
|
141
|
+
API_ROUTES.skills.update,
|
|
142
|
+
async (req: Request<{ name: string }, unknown, UpdateSkillBody>, res: Response<UpdateSkillResponse | ErrorResponse>) => {
|
|
143
|
+
const { name } = req.params;
|
|
144
|
+
const { description, body } = req.body ?? {};
|
|
145
|
+
log.info("skills", "update: start", { name });
|
|
146
|
+
if (typeof description !== "string") {
|
|
147
|
+
log.warn("skills", "update: invalid description", { name });
|
|
148
|
+
badRequest(res, "description must be a string");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (typeof body !== "string") {
|
|
152
|
+
log.warn("skills", "update: invalid body", { name });
|
|
153
|
+
badRequest(res, "body must be a string");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const result = await updateProjectSkill({
|
|
157
|
+
workspaceRoot: workspacePath,
|
|
158
|
+
name,
|
|
159
|
+
description,
|
|
160
|
+
body,
|
|
161
|
+
});
|
|
162
|
+
if (result.kind === "updated") {
|
|
163
|
+
log.info("skills", "updated", { name });
|
|
164
|
+
refreshScheduledSkills().catch(logBackgroundError("skills"));
|
|
165
|
+
res.json({ updated: true, path: result.path });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (result.kind === "invalid-slug") {
|
|
169
|
+
log.warn("skills", "update: invalid slug", { slug: result.slug });
|
|
170
|
+
badRequest(res, `invalid slug: "${result.slug}"`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (result.kind === "missing-field") {
|
|
174
|
+
log.warn("skills", "update: missing field", { name, field: result.field });
|
|
175
|
+
badRequest(res, `${result.field} must be a non-empty string`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (result.kind === "user-scope") {
|
|
179
|
+
log.warn("skills", "update: user scope refused", { name: result.name });
|
|
180
|
+
forbidden(res, `cannot update user-scope skill "${result.name}" — only project-scope skills are writable.`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (result.kind === "not-found") {
|
|
184
|
+
log.warn("skills", "update: not found", { name: result.name });
|
|
185
|
+
notFound(res, `skill not found: ${result.name}`);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
bindRoute(router, API_ROUTES.skills.remove, async (req: Request<{ name: string }>, res: Response<DeleteSkillResponse | ErrorResponse>) => {
|
|
186
191
|
log.info("skills", "delete: start", { name: req.params.name });
|
|
187
192
|
const result = await deleteProjectSkill({
|
|
188
193
|
workspaceRoot: workspacePath,
|