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
package/server/utils/gemini.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GoogleGenAI, type GenerateContentParameters } from "@google/genai";
|
|
1
|
+
import { GoogleGenAI, type GenerateContentParameters, type GenerateContentResponse, type Part } from "@google/genai";
|
|
2
2
|
import { env } from "../system/env.js";
|
|
3
3
|
import { log } from "../system/logger/index.js";
|
|
4
4
|
import { errorMessage } from "./errors.js";
|
|
@@ -29,6 +29,32 @@ export interface GeminiImageResult {
|
|
|
29
29
|
message?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// Pull the first candidate's `content.parts` array out of a Gemini
|
|
33
|
+
// response, defaulting to `[]` when any layer of the optional chain is
|
|
34
|
+
// absent. Pure — exported for unit tests.
|
|
35
|
+
export function firstCandidateParts(response: GenerateContentResponse): readonly Part[] {
|
|
36
|
+
return response.candidates?.[0]?.content?.parts ?? [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Pull the first candidate's `finishReason` (used in debug logs).
|
|
40
|
+
// Pure — exported for unit tests.
|
|
41
|
+
export function firstFinishReason(response: GenerateContentResponse): string | undefined {
|
|
42
|
+
return response.candidates?.[0]?.finishReason;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Reduce a Gemini response's `parts` array down to the {imageData,
|
|
46
|
+
// message} pair the rest of the app cares about. Last text wins; last
|
|
47
|
+
// inline-image wins. Parts without text or `inlineData.data` are
|
|
48
|
+
// skipped. Pure — exported for unit tests.
|
|
49
|
+
export function extractImageResult(parts: readonly Part[]): GeminiImageResult {
|
|
50
|
+
const result: GeminiImageResult = {};
|
|
51
|
+
for (const part of parts) {
|
|
52
|
+
if (part.text) result.message = part.text;
|
|
53
|
+
if (part.inlineData?.data) result.imageData = part.inlineData.data;
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
// Low-level wrapper around `ai.models.generateContent` that pulls
|
|
33
59
|
// the first inline image and text part out of the response. Use this
|
|
34
60
|
// when you need to pass custom `contents` (e.g. text + reference
|
|
@@ -48,7 +74,7 @@ export async function generateGeminiImageContent(
|
|
|
48
74
|
const client = getGeminiClient();
|
|
49
75
|
log.debug("gemini", "generateContent: request", {
|
|
50
76
|
model,
|
|
51
|
-
hasConfig:
|
|
77
|
+
hasConfig: Boolean(config),
|
|
52
78
|
aspectRatio: config?.imageConfig?.aspectRatio,
|
|
53
79
|
});
|
|
54
80
|
let response;
|
|
@@ -62,18 +88,14 @@ export async function generateGeminiImageContent(
|
|
|
62
88
|
log.debug("gemini", "generateContent: SDK threw", { model, error: errorMessage(err) });
|
|
63
89
|
throw err;
|
|
64
90
|
}
|
|
65
|
-
const parts = response
|
|
66
|
-
const result
|
|
67
|
-
for (const part of parts) {
|
|
68
|
-
if (part.text) result.message = part.text;
|
|
69
|
-
if (part.inlineData?.data) result.imageData = part.inlineData.data;
|
|
70
|
-
}
|
|
91
|
+
const parts = firstCandidateParts(response);
|
|
92
|
+
const result = extractImageResult(parts);
|
|
71
93
|
log.debug("gemini", "generateContent: response", {
|
|
72
94
|
model,
|
|
73
95
|
parts: parts.length,
|
|
74
|
-
hasImage:
|
|
75
|
-
hasText:
|
|
76
|
-
finishReason: response
|
|
96
|
+
hasImage: Boolean(result.imageData),
|
|
97
|
+
hasText: Boolean(result.message),
|
|
98
|
+
finishReason: firstFinishReason(response),
|
|
77
99
|
});
|
|
78
100
|
return result;
|
|
79
101
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Read an HTML artifact file under the `/artifacts/html` static
|
|
2
|
+
// mount and splice two helper scripts before its closing `</body>`:
|
|
3
|
+
// 1. The image-self-repair script (#1011 Stage E / #1025) — restores
|
|
4
|
+
// the behavior that PR #980 unhooked when presentHtml / Files
|
|
5
|
+
// HTML preview moved off `srcdoc` onto the static mount.
|
|
6
|
+
// 2. The iframe height reporter (#1219 follow-up) — lets the parent
|
|
7
|
+
// StackView size the iframe to its rendered content despite the
|
|
8
|
+
// `sandbox="allow-scripts"` cross-origin barrier.
|
|
9
|
+
//
|
|
10
|
+
// Both scripts are pure string injections of trusted server code; the
|
|
11
|
+
// helpers are pure functions and order doesn't matter (one-shot scripts
|
|
12
|
+
// that observe their own document state).
|
|
13
|
+
//
|
|
14
|
+
// Lives in its own module — not inline in `server/index.ts` — so unit
|
|
15
|
+
// tests can import the helpers without dragging the entire server
|
|
16
|
+
// startup as an import side effect.
|
|
17
|
+
|
|
18
|
+
import { readFile as fsReadFile } from "fs/promises";
|
|
19
|
+
import { resolveWithinRoot } from "../files/safe.js";
|
|
20
|
+
import { injectImageRepairScript } from "../../../src/utils/image/imageRepairInlineScript.js";
|
|
21
|
+
import { injectHeightReporterScript } from "../../../src/utils/html/iframeHeightReporterScript.js";
|
|
22
|
+
|
|
23
|
+
/** Read an HTML artifact file (under `htmlsRoot`) and splice the
|
|
24
|
+
* image-self-repair script before its closing `</body>`. Returns
|
|
25
|
+
* the spliced HTML on success, `null` when the file can't be
|
|
26
|
+
* resolved (escapes the root) or read (missing / unreadable).
|
|
27
|
+
*
|
|
28
|
+
* `htmlsRoot` MUST already be a realpath — `resolveWithinRoot`
|
|
29
|
+
* compares against it strictly. The middleware in `server/index.ts`
|
|
30
|
+
* passes the cached `getHtmlsDirReal()` result, which is a realpath. */
|
|
31
|
+
export async function readAndInjectHtmlArtifact(htmlsRoot: string, relPath: string): Promise<string | null> {
|
|
32
|
+
const abs = resolveWithinRoot(htmlsRoot, relPath);
|
|
33
|
+
if (!abs) return null;
|
|
34
|
+
let raw: string;
|
|
35
|
+
try {
|
|
36
|
+
raw = await fsReadFile(abs, "utf8");
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return injectHeightReporterScript(injectImageRepairScript(raw));
|
|
41
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Server-side mirror of `src/utils/markdown/frontmatter.ts`
|
|
2
|
+
// (#895 PR B). The two share the same shape on purpose so the
|
|
3
|
+
// parser/serializer/merger contract is identical on both sides
|
|
4
|
+
// — what writeWikiPage emits round-trips losslessly through the
|
|
5
|
+
// Vue useMarkdownDoc composable.
|
|
6
|
+
//
|
|
7
|
+
// Code is intentionally NOT a shared package: the wrapper is ~10
|
|
8
|
+
// lines on each side, and a workspace package would need cross-
|
|
9
|
+
// build wiring (browser bundle for Vue, plain Node for server)
|
|
10
|
+
// that's not worth it for so little glue. js-yaml does the heavy
|
|
11
|
+
// lifting in both places, identically.
|
|
12
|
+
|
|
13
|
+
import yaml from "js-yaml";
|
|
14
|
+
|
|
15
|
+
export interface ParsedMarkdown {
|
|
16
|
+
/** Parsed YAML object. Empty `{}` when the document has no
|
|
17
|
+
* frontmatter or the YAML failed to parse. Insertion order
|
|
18
|
+
* matches the source so callers iterating `Object.entries`
|
|
19
|
+
* see fields in the order the file declared them. */
|
|
20
|
+
meta: Record<string, unknown>;
|
|
21
|
+
/** Body after stripping the frontmatter envelope. The trailing
|
|
22
|
+
* newline of the closing `---` line is consumed; a no-frontmatter
|
|
23
|
+
* document returns the raw input verbatim. */
|
|
24
|
+
body: string;
|
|
25
|
+
/** True iff a well-formed `---\n...\n---\n` envelope was
|
|
26
|
+
* detected and parsed. Malformed YAML inside an envelope
|
|
27
|
+
* degrades to `hasHeader: false` so a typo in the header
|
|
28
|
+
* doesn't break writes. */
|
|
29
|
+
hasHeader: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const FRONTMATTER_OPEN = /^---\r?\n/;
|
|
33
|
+
// `(?:^|\r?\n)` lets the closing fence sit at the very start of
|
|
34
|
+
// `afterOpen` — needed for empty envelopes (`---\n---\n`) where
|
|
35
|
+
// the closing `---` is the first thing after the open is stripped.
|
|
36
|
+
const FRONTMATTER_CLOSE = /(?:^|\r?\n)---\s*(?:\r?\n|$)/;
|
|
37
|
+
|
|
38
|
+
/** Parse a markdown document, splitting frontmatter from body.
|
|
39
|
+
* Always returns an object — never throws. */
|
|
40
|
+
export function parseFrontmatter(raw: string): ParsedMarkdown {
|
|
41
|
+
if (!FRONTMATTER_OPEN.test(raw)) {
|
|
42
|
+
return { meta: {}, body: raw, hasHeader: false };
|
|
43
|
+
}
|
|
44
|
+
const afterOpen = raw.replace(FRONTMATTER_OPEN, "");
|
|
45
|
+
const closeMatch = FRONTMATTER_CLOSE.exec(afterOpen);
|
|
46
|
+
if (!closeMatch || closeMatch.index === undefined) {
|
|
47
|
+
return { meta: {}, body: raw, hasHeader: false };
|
|
48
|
+
}
|
|
49
|
+
const yamlText = afterOpen.slice(0, closeMatch.index);
|
|
50
|
+
const body = afterOpen.slice(closeMatch.index + closeMatch[0].length);
|
|
51
|
+
const meta = safeYamlLoad(yamlText);
|
|
52
|
+
if (meta === null) {
|
|
53
|
+
return { meta: {}, body: raw, hasHeader: false };
|
|
54
|
+
}
|
|
55
|
+
return { meta, body, hasHeader: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Serialize a meta object + body back into the canonical
|
|
59
|
+
* `---\n...\n---\n\nbody` shape. An empty `meta` returns the body
|
|
60
|
+
* alone (no envelope) — the lazy-on-write contract: don't add
|
|
61
|
+
* ceremony to documents that don't have anything to record.
|
|
62
|
+
*
|
|
63
|
+
* Round-trip semantics: VALUE-preserving, NOT byte-preserving.
|
|
64
|
+
* `js-yaml` adds quotes to ambiguous scalars (`'1.20'`, `'true'`)
|
|
65
|
+
* so they parse back as the same string under FAILSAFE_SCHEMA.
|
|
66
|
+
* Source-text formatting may change on save but the parsed value
|
|
67
|
+
* is stable across rounds. */
|
|
68
|
+
export function serializeWithFrontmatter(meta: Record<string, unknown>, body: string): string {
|
|
69
|
+
if (Object.keys(meta).length === 0) return body;
|
|
70
|
+
// `lineWidth: -1` disables auto-wrap so long URLs / titles stay on
|
|
71
|
+
// one line. `noRefs: true` avoids YAML anchor syntax (`&id001`)
|
|
72
|
+
// which is technically valid but visually noisy in plain-text
|
|
73
|
+
// markdown.
|
|
74
|
+
const yamlText = yaml.dump(meta, { lineWidth: -1, noRefs: true }).trimEnd();
|
|
75
|
+
return `---\n${yamlText}\n---\n\n${body}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Merge a patch into an existing meta object. Unknown keys in
|
|
79
|
+
* `existing` are preserved verbatim; keys present in `patch`
|
|
80
|
+
* overwrite. A `null` or `undefined` patch value DELETES the key
|
|
81
|
+
* (REST PATCH semantics) — callers that want "leave alone"
|
|
82
|
+
* should omit the key entirely. */
|
|
83
|
+
export function mergeFrontmatter(existing: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {
|
|
84
|
+
const out: Record<string, unknown> = { ...existing };
|
|
85
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
86
|
+
if (value === null || value === undefined) {
|
|
87
|
+
Reflect.deleteProperty(out, key);
|
|
88
|
+
} else {
|
|
89
|
+
out[key] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function safeYamlLoad(text: string): Record<string, unknown> | null {
|
|
96
|
+
try {
|
|
97
|
+
// `FAILSAFE_SCHEMA` keeps every scalar as a string and skips
|
|
98
|
+
// type coercion. Two motivating cases:
|
|
99
|
+
// - YAML 1.1 dates (`created: 2026-04-27`) would become a
|
|
100
|
+
// `Date` object under DEFAULT_SCHEMA, breaking round-trip.
|
|
101
|
+
// - Numeric-looking strings (`version: 1.20` → 1.2 under
|
|
102
|
+
// JSON_SCHEMA) drop trailing zeros on save.
|
|
103
|
+
// For the wiki-history use case — title / created / updated /
|
|
104
|
+
// tags / editor — every value that should be a string IS one.
|
|
105
|
+
const loaded = yaml.load(text, { schema: yaml.FAILSAFE_SCHEMA });
|
|
106
|
+
if (loaded === null || loaded === undefined) return {};
|
|
107
|
+
if (typeof loaded !== "object" || Array.isArray(loaded)) return null;
|
|
108
|
+
return loaded as Record<string, unknown>;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Central audit point for server-side regular expressions.
|
|
2
|
+
//
|
|
3
|
+
// Why one file? `eslint-plugin-security`'s `detect-unsafe-regex` rule
|
|
4
|
+
// fires on any regex with a non-trivial nested-quantifier shape, even
|
|
5
|
+
// when surrounding analysis shows the pattern is bounded. Rather than
|
|
6
|
+
// scatter `eslint-disable-next-line` annotations across the server
|
|
7
|
+
// tree, every server regex that the rule flagged lives here with its
|
|
8
|
+
// ReDoS-safety rationale spelled out — security reviews start at this
|
|
9
|
+
// file.
|
|
10
|
+
//
|
|
11
|
+
// Browser-side regexes (`src/utils/format/jsonSyntax.ts`,
|
|
12
|
+
// `src/utils/markdown/taskList.ts`) stay in their consumer files
|
|
13
|
+
// because moving them would force the frontend to import from
|
|
14
|
+
// `server/`. A symmetric `src/utils/regex.ts` could be added later;
|
|
15
|
+
// kept out of scope here so this file has only one architectural
|
|
16
|
+
// layer.
|
|
17
|
+
|
|
18
|
+
// ── Slug validator ────────────────────────────────────────────────
|
|
19
|
+
//
|
|
20
|
+
// Used by `server/utils/slug.ts#isValidSlug` and (transitively)
|
|
21
|
+
// every domain that slugifies user input — todos, wiki, sources,
|
|
22
|
+
// skills. Linear in input length; `[a-z0-9-]*` and `[a-z0-9]` don't
|
|
23
|
+
// share characters with each other or with the boundary `^` / `$`,
|
|
24
|
+
// so the optional capture group can't drive backtracking. Caller
|
|
25
|
+
// length-caps input at `DEFAULT_MAX_LENGTH` (120 chars) BEFORE
|
|
26
|
+
// invoking the regex, so worst-case is ~120 character comparisons.
|
|
27
|
+
//
|
|
28
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- bounded slug validator (length-capped by caller; no nested-quantifier overlap)
|
|
29
|
+
export const SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
30
|
+
|
|
31
|
+
// ── Wiki bullet-link parsers ──────────────────────────────────────
|
|
32
|
+
//
|
|
33
|
+
// Used by `server/api/routes/wiki.ts` to parse top-of-page index
|
|
34
|
+
// bullets like:
|
|
35
|
+
// - [Title](path/to/page.md) — optional summary
|
|
36
|
+
// - [[wiki-style title]] — optional summary
|
|
37
|
+
// Each `[^x]+` runs over a fixed exclusion set with a hard delimiter
|
|
38
|
+
// (`]`, `)`); the optional summary group's `(.*)` is a single greedy
|
|
39
|
+
// run, no nested overlap. Linear in line length.
|
|
40
|
+
//
|
|
41
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- bullet-link parser; bounded captures with hard delimiters
|
|
42
|
+
export const BULLET_LINK_PATTERN = /^[-*]\s+\[([^\]]+)\]\(([^)]*)\)(?:\s*[—–-]\s*(.*))?/;
|
|
43
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- same shape as BULLET_LINK_PATTERN
|
|
44
|
+
export const BULLET_WIKI_LINK_PATTERN = /^[-*]\s+\[\[([^\]]+)\]\](?:\s*[—–-]\s*(.*))?/;
|
|
45
|
+
|
|
46
|
+
// ── Skills body blank-line stripper ───────────────────────────────
|
|
47
|
+
//
|
|
48
|
+
// Used by `server/workspace/skills/parser.ts` to drop leading blank
|
|
49
|
+
// lines from a skill's body after stripping the frontmatter. `\s*\n`
|
|
50
|
+
// consumes one line at a time with optional leading whitespace; the
|
|
51
|
+
// outer `+` repeats over distinct lines (each iteration MUST consume
|
|
52
|
+
// at least the trailing `\n`), so no overlap → linear in input
|
|
53
|
+
// length.
|
|
54
|
+
//
|
|
55
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- linear blank-line stripper, no nested-quantifier overlap
|
|
56
|
+
export const LEADING_BLANK_LINES_PATTERN = /^(?:\s*\n)+/;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Tiny adapter for registering Express routes from a `ResolvedRoute`
|
|
2
|
+
// `{ method, url }` tuple — the shape host aggregators emit for every
|
|
3
|
+
// plugin-owned route. Lets route files spell the registration as
|
|
4
|
+
// `bindRoute(router, API_ROUTES.todos.itemsCreate, handler)` instead
|
|
5
|
+
// of branching on the verb at the call site.
|
|
6
|
+
//
|
|
7
|
+
// Handler typing matches Express's own loose `RouteParameters`
|
|
8
|
+
// inference: a handler typed as `(req: Request<{ id: string }>, res:
|
|
9
|
+
// Response) => void` is forwarded as-is — the underlying
|
|
10
|
+
// `router[method](url, handler)` call accepts any handler shape, so
|
|
11
|
+
// we cast through `unknown` once here rather than at every site.
|
|
12
|
+
|
|
13
|
+
import type { IRouter, RequestHandler } from "express";
|
|
14
|
+
import type { ResolvedRoute } from "../../src/plugins/meta-types.js";
|
|
15
|
+
|
|
16
|
+
/** Register `handler` on `router` using the verb + URL declared by
|
|
17
|
+
* `route`. The generic on `handler` lets callers pass an Express
|
|
18
|
+
* handler typed against their own `params` / `body` / `query`
|
|
19
|
+
* generics; the cast hides the variance from Express's
|
|
20
|
+
* `RequestHandler<ParamsDictionary, …>` default. */
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
export function bindRoute<H extends (...args: any[]) => unknown>(router: IRouter, route: ResolvedRoute, ...handlers: H[]): void {
|
|
23
|
+
const cast = handlers as unknown as RequestHandler[];
|
|
24
|
+
switch (route.method) {
|
|
25
|
+
case "GET":
|
|
26
|
+
router.get(route.url, ...cast);
|
|
27
|
+
break;
|
|
28
|
+
case "POST":
|
|
29
|
+
router.post(route.url, ...cast);
|
|
30
|
+
break;
|
|
31
|
+
case "PUT":
|
|
32
|
+
router.put(route.url, ...cast);
|
|
33
|
+
break;
|
|
34
|
+
case "PATCH":
|
|
35
|
+
router.patch(route.url, ...cast);
|
|
36
|
+
break;
|
|
37
|
+
case "DELETE":
|
|
38
|
+
router.delete(route.url, ...cast);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
package/server/utils/slug.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "crypto";
|
|
2
|
+
import { SLUG_PATTERN } from "./regex.js";
|
|
2
3
|
|
|
3
4
|
// Bits of sha256 kept as the non-ASCII fallback id. 16 base64url chars =
|
|
4
5
|
// 96 bits; birthday-collision expectation lives at ~2^48 entries, so
|
|
@@ -37,7 +38,8 @@ export function hashSlug(input: string, length: number = NON_ASCII_HASH_LEN): st
|
|
|
37
38
|
export function isValidSlug(slug: string): boolean {
|
|
38
39
|
if (typeof slug !== "string") return false;
|
|
39
40
|
if (slug.length === 0 || slug.length > DEFAULT_MAX_LENGTH) return false;
|
|
40
|
-
|
|
41
|
+
// Pattern + ReDoS-safety rationale lives in `server/utils/regex.ts`.
|
|
42
|
+
if (!SLUG_PATTERN.test(slug)) return false;
|
|
41
43
|
if (slug.includes("--")) return false;
|
|
42
44
|
return true;
|
|
43
45
|
}
|
|
@@ -104,8 +106,8 @@ export function disambiguateSlug(base: string, existingIds: ReadonlySet<string>)
|
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
function hasNonHyphenChar(input: string): boolean {
|
|
107
|
-
for (
|
|
108
|
-
if (
|
|
109
|
+
for (const char of input) {
|
|
110
|
+
if (char !== "-") return true;
|
|
109
111
|
}
|
|
110
112
|
return false;
|
|
111
113
|
}
|
package/server/utils/time.ts
CHANGED
|
@@ -24,6 +24,18 @@ export const TIME_UNIT_MS: Record<string, number> = {
|
|
|
24
24
|
/** Quick subprocess probe (docker ps, libreoffice --version, etc.) */
|
|
25
25
|
export const SUBPROCESS_PROBE_TIMEOUT_MS = 5 * ONE_SECOND_MS;
|
|
26
26
|
|
|
27
|
+
/** Debounce window for dev-plugin `dist/` watcher (#1159 PR3). Vite
|
|
28
|
+
* writes 4-5 files within ~100ms on a single rebuild; 300ms collapses
|
|
29
|
+
* the burst into one publish. */
|
|
30
|
+
export const DEV_PLUGIN_WATCH_DEBOUNCE_MS = 300;
|
|
31
|
+
|
|
32
|
+
/** Hard cap on how long the startup-failure path waits for
|
|
33
|
+
* `httpServer.close()` to drain in-flight connections before
|
|
34
|
+
* forcing `process.exit(1)`. SSE streams + WebSocket upgrades hold
|
|
35
|
+
* connections open indefinitely, so the graceful close alone isn't
|
|
36
|
+
* a fail-fast guarantee. */
|
|
37
|
+
export const STARTUP_FAILURE_FORCE_EXIT_MS = 5 * ONE_SECOND_MS;
|
|
38
|
+
|
|
27
39
|
/** Heavy subprocess work (libreoffice conversion, etc.) */
|
|
28
40
|
export const SUBPROCESS_WORK_TIMEOUT_MS = ONE_MINUTE_MS;
|
|
29
41
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// at a `mkdtempSync` directory without touching the real
|
|
9
9
|
// ~/mulmoclaude.
|
|
10
10
|
|
|
11
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
11
|
+
import { readdir, readFile, rm } from "node:fs/promises";
|
|
12
12
|
import { defaultSummarize, loadJsonlInput, type SummarizeFn } from "./summarizer.js";
|
|
13
13
|
import { chatDirFor, indexEntryPathFor, manifestPathFor, sessionJsonlPathFor, sessionMetaPathFor } from "./paths.js";
|
|
14
14
|
import type { ChatIndexEntry, ChatIndexManifest } from "./types.js";
|
|
@@ -104,6 +104,19 @@ export async function updateManifest(workspaceRoot: string, mutator: (m: ChatInd
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Drop a session's entry from both the per-session index file and
|
|
108
|
+
// the shared manifest. Used by the sessions hard-delete route so AI
|
|
109
|
+
// title / summary / keywords don't outlive the underlying jsonl on
|
|
110
|
+
// disk. Both removals tolerate "missing" — sessions that were never
|
|
111
|
+
// indexed have no entry to prune.
|
|
112
|
+
export async function removeSessionFromIndex(workspaceRoot: string, sessionId: string): Promise<void> {
|
|
113
|
+
await rm(indexEntryPathFor(workspaceRoot, sessionId), { force: true });
|
|
114
|
+
await updateManifest(workspaceRoot, (manifest) => ({
|
|
115
|
+
...manifest,
|
|
116
|
+
entries: manifest.entries.filter((entry) => entry.id !== sessionId),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
// --- freshness check ------------------------------------------------
|
|
108
121
|
|
|
109
122
|
// A session is "fresh" when its per-session index file exists and
|
|
@@ -115,7 +128,7 @@ export async function isFresh(workspaceRoot: string, sessionId: string, now: num
|
|
|
115
128
|
const raw = await readFile(indexEntryPathFor(workspaceRoot, sessionId), "utf-8");
|
|
116
129
|
const entry: unknown = JSON.parse(raw);
|
|
117
130
|
if (!isRecord(entry)) return false;
|
|
118
|
-
const indexedAt =
|
|
131
|
+
const { indexedAt } = entry as Record<string, unknown>;
|
|
119
132
|
if (typeof indexedAt !== "string") return false;
|
|
120
133
|
const indexedTimestamp = Date.parse(indexedAt);
|
|
121
134
|
if (Number.isNaN(indexedTimestamp)) return false;
|
|
@@ -86,7 +86,7 @@ export function extractText(jsonlContent: string): string {
|
|
|
86
86
|
} catch {
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
|
-
const source = entry
|
|
89
|
+
const { source } = entry;
|
|
90
90
|
if ((source === "user" || source === "assistant") && entry.type === EVENT_TYPES.text && typeof entry.message === "string") {
|
|
91
91
|
parts.push(`[${source}] ${trimMessage(entry.message)}`);
|
|
92
92
|
}
|
|
@@ -64,7 +64,7 @@ function validatePath(rawPath: string): string | null {
|
|
|
64
64
|
if (path.isAbsolute(normalized)) return null;
|
|
65
65
|
|
|
66
66
|
// Not a reserved system directory
|
|
67
|
-
if (RESERVED_DIRS.some((reservedDir) => normalized === reservedDir || normalized.startsWith(reservedDir
|
|
67
|
+
if (RESERVED_DIRS.some((reservedDir) => normalized === reservedDir || normalized.startsWith(`${reservedDir}/`))) {
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -9,7 +9,7 @@ A single key unlocks all three capabilities (images, TTS audio, video) — you d
|
|
|
9
9
|
### Images
|
|
10
10
|
|
|
11
11
|
- **`generateImage`** — creates images from text prompts. The heart of the **Artist** role.
|
|
12
|
-
- **`
|
|
12
|
+
- **`editImages`** — transforms, restyles, or combines up to 8 existing images per call ("convert to Ghibli style", "remove the background", "merge these two photos into one"). Also **Artist**.
|
|
13
13
|
- **Inline document images** — roles that produce rich documents (**Guide & Planner**, **Tutor**, Recipe Guide, Trip Planner, …) embed generated images directly into the page via `presentDocument`. Without a key, those image slots fall back to italic "🖼️ Image: <prompt>" text markers — the prompt is preserved, but no picture renders.
|
|
14
14
|
- **MulmoScript image beats** — `presentMulmoScript` uses Gemini image models for `imagePrompt` beats. **Storyteller Plus** additionally uses them for consistent-character scenes across a storyboard.
|
|
15
15
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Guide & Planner Templates
|
|
2
|
+
|
|
3
|
+
Reference templates for the Guide & Planner role. Use these structures when authoring documents with `presentDocument` for any of the supported guide types.
|
|
4
|
+
|
|
5
|
+
## Document Conventions (all types)
|
|
6
|
+
|
|
7
|
+
Every guide should:
|
|
8
|
+
|
|
9
|
+
- **Overview**: open with a summary of the key parameters (servings, days, level, budget, etc.)
|
|
10
|
+
- **Numbered steps OR structured sections**: use one or the other consistently across the document
|
|
11
|
+
- **Anchors**: add `<a id="step-1"></a>` (or `step-N`, `day-N`, `section-name`) to each major heading for `scrollToAnchor` navigation
|
|
12
|
+
- **Images**: embed via `` — make the alt-text prompt specific enough to generate a useful illustration on its own
|
|
13
|
+
- **Close**: end with tips, variations, troubleshooting, or follow-up recommendations
|
|
14
|
+
- **Tone**: warm and encouraging; adapt vocabulary to the user's stated experience level
|
|
15
|
+
|
|
16
|
+
## Form-First Workflow
|
|
17
|
+
|
|
18
|
+
Always call `presentForm` before producing the document. Tailor fields to the request type:
|
|
19
|
+
|
|
20
|
+
- **Recipe**: servings, dietary restrictions, skill level, available time, equipment
|
|
21
|
+
- **Travel**: destination, dates, traveler count, interests, budget tier, accommodation type
|
|
22
|
+
- **Fitness**: goal (weight loss / strength / endurance), starting level, days per week, equipment access
|
|
23
|
+
- **Event**: occasion, guest count, budget, venue type, dietary needs
|
|
24
|
+
- **Study guide**: topic, current level, time available, learning goal
|
|
25
|
+
- **DIY / home project**: project type, skill level, tools available, budget
|
|
26
|
+
|
|
27
|
+
Pre-fill any field the user already provided via `defaultValue`. Mark fields the user must answer as `required: true`. Keep forms concise — ask only for what is needed to produce a great result.
|
|
28
|
+
|
|
29
|
+
## Per-Type Document Structures
|
|
30
|
+
|
|
31
|
+
### Recipe
|
|
32
|
+
|
|
33
|
+
overview → ingredients (scaled to the chosen servings) → equipment → prep work → numbered cooking steps (each with an image) → chef's tips → storage notes
|
|
34
|
+
|
|
35
|
+
### Travel
|
|
36
|
+
|
|
37
|
+
overview → day-by-day itinerary (morning / afternoon / evening) → accommodation & dining → transport between stops → budget breakdown → packing tips → local culture tips
|
|
38
|
+
|
|
39
|
+
### Fitness
|
|
40
|
+
|
|
41
|
+
overview → weekly schedule → per-workout breakdown (warm-up, main exercises with sets / reps / form notes, cool-down) → progression plan over weeks → nutrition tips
|
|
42
|
+
|
|
43
|
+
### Event
|
|
44
|
+
|
|
45
|
+
overview → timeline & checklist (T-minus weeks) → venue & catering → guest list & invitations → décor & entertainment → budget tracker
|
|
46
|
+
|
|
47
|
+
### Study Guide
|
|
48
|
+
|
|
49
|
+
overview → topic breakdown → key concepts per section → worked examples → practice questions → resources & references
|
|
50
|
+
|
|
51
|
+
### DIY / Home Project
|
|
52
|
+
|
|
53
|
+
overview → required tools & materials → safety notes → numbered steps (each with an image) → finishing & cleanup → maintenance & care
|
|
54
|
+
|
|
55
|
+
## Follow-up Pattern
|
|
56
|
+
|
|
57
|
+
After presenting the document:
|
|
58
|
+
|
|
59
|
+
- Offer to read any step aloud (scroll to it first with `scrollToAnchor`, then narrate the step)
|
|
60
|
+
- Invite follow-up questions
|
|
61
|
+
- Offer to adjust the plan based on feedback (regenerate the form, add steps, change scope)
|
|
@@ -15,6 +15,7 @@ Under the hood it uses the Claude Code Agent SDK as its LLM core. Claude has ful
|
|
|
15
15
|
- **Tutor** — Assesses your knowledge level, then teaches any topic with structured documents and visuals.
|
|
16
16
|
- **Storyteller** — Crafts illustrated narrative stories as a MulmoScript storyboard.
|
|
17
17
|
- **Storyteller Plus** — Like Storyteller, with consistent character images across beats.
|
|
18
|
+
- **Settings** — Manages information sources, skills, and scheduled automations for the workspace.
|
|
18
19
|
- *(Additional roles may be defined by the user in the workspace.)*
|
|
19
20
|
|
|
20
21
|
## Key Capabilities
|
|
@@ -42,7 +43,10 @@ See [Wiki](helps/wiki.md) for details on how it works.
|
|
|
42
43
|
- [Gemini API Key](helps/gemini.md) — why `GEMINI_API_KEY` is strongly recommended (images, audio, video) and how to get one from Google AI Studio
|
|
43
44
|
- [MulmoScript](helps/mulmoscript.md) — format reference for authoring multimedia stories: beats, image types, speech, audio, and a minimal example
|
|
44
45
|
- [Business Presentation Template](helps/business.md) — MulmoScript template and rules for business presentations in the Office role
|
|
46
|
+
- [Storyteller Template](helps/storyteller.md) — MulmoScript template and rules for character-driven narrated stories in the Storyteller role
|
|
47
|
+
- [Guide & Planner Templates](helps/guide.md) — document structures and form-field hints per guide type for the Guide & Planner role
|
|
45
48
|
- [Spreadsheet](helps/spreadsheet.md) — cell format, formulas, date handling, and format codes for the presentSpreadsheet plugin
|
|
49
|
+
- [presentHtml](helps/presenthtml.md) — self-contained HTML rules and the three-`../` relative-path convention used by the presentHtml plugin to keep generated files portable under `file://`
|
|
46
50
|
- [Sandbox](helps/sandbox.md) — how the Docker sandbox isolates the agent, what it can access, and how to disable it
|
|
47
51
|
- [Telegram Bridge](helps/telegram.md) — how to talk to MulmoClaude from the Telegram app: creating a bot, starting the bridge, allowlisting chat IDs, commands, and troubleshooting
|
|
48
52
|
- [Information Sources](helps/sources.md) — registering RSS feeds / GitHub repos / arXiv queries, the daily-brief pipeline, and where its files live on disk
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# presentHtml — Authoring Guide
|
|
2
|
+
|
|
3
|
+
Reference for the `presentHtml` plugin. The HTML you provide is saved to `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html` and rendered in the canvas. The user may also open the file directly from disk via `file://`, so it must remain portable: every URL inside the document has to resolve both when served by the app and when loaded straight off the filesystem.
|
|
4
|
+
|
|
5
|
+
## Self-Contained Document
|
|
6
|
+
|
|
7
|
+
- Full document including `<!DOCTYPE html>` and `<html>` / `<body>` tags.
|
|
8
|
+
- All CSS and JavaScript inline, or loaded via a public CDN. No local script / stylesheet files (the app does not host arbitrary `.css` / `.js`).
|
|
9
|
+
|
|
10
|
+
### Allowed CDNs
|
|
11
|
+
|
|
12
|
+
The preview iframe enforces a CSP that only permits a curated set of CDNs. **Use these origins or your script / stylesheet will be silently blocked** and the page will render broken (e.g. `Plotly is not defined` if the chart library was blocked):
|
|
13
|
+
|
|
14
|
+
- `https://cdn.jsdelivr.net` — broadest coverage; preferred for any npm-shaped library
|
|
15
|
+
- `https://unpkg.com` — same scope as jsdelivr; fallback
|
|
16
|
+
- `https://cdnjs.cloudflare.com` — curated mirror; common for older libraries
|
|
17
|
+
- `https://fonts.googleapis.com` + `https://fonts.gstatic.com` — Google Fonts
|
|
18
|
+
- `https://cdn.plot.ly` — Plotly's first-party CDN (also reachable via jsdelivr)
|
|
19
|
+
|
|
20
|
+
When in doubt, pull from `cdn.jsdelivr.net` — it mirrors most npm packages and is always safe. Examples:
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- Plotly via jsdelivr (preferred) -->
|
|
24
|
+
<script src="https://cdn.jsdelivr.net/npm/plotly.js-dist@2/plotly.min.js"></script>
|
|
25
|
+
|
|
26
|
+
<!-- D3 -->
|
|
27
|
+
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
|
28
|
+
|
|
29
|
+
<!-- Tailwind (browser play CDN) -->
|
|
30
|
+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If a library you need is hosted only on a CDN not in this list (e.g. `cdn.example.org`), inline the library code directly — do not link to an unlisted host.
|
|
34
|
+
|
|
35
|
+
## Referencing Workspace Files
|
|
36
|
+
|
|
37
|
+
The output HTML lives three directory levels deep under `artifacts/`. To reference an image / chart / other artifact, use a **relative path with exactly three `../`** to climb out of `html/<YYYY>/<MM>/`:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<!-- GOOD -->
|
|
41
|
+
<img src="../../../images/2026/04/foo.png">
|
|
42
|
+
|
|
43
|
+
<!-- BAD: absolute path breaks under file:// -->
|
|
44
|
+
<img src="/artifacts/images/2026/04/foo.png">
|
|
45
|
+
|
|
46
|
+
<!-- BAD: workspace-rooted path resolves against the page URL -->
|
|
47
|
+
<img src="artifacts/images/2026/04/foo.png">
|
|
48
|
+
|
|
49
|
+
<!-- BAD: runtime artifact, not a stored convention -->
|
|
50
|
+
<img src="/api/files/raw?path=artifacts/images/2026/04/foo.png">
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Workspace paths returned by other tools (e.g. an image-generating tool returns `artifacts/images/2026/04/foo.png`): replace the leading `artifacts/` with `../../../`, giving `../../../images/2026/04/foo.png`.
|
|
54
|
+
|
|
55
|
+
The same rule applies to anywhere a path appears: `<img>`, `<source>`, `<video poster>`, `<audio src>`, CSS `url(...)`, etc.
|
|
56
|
+
|
|
57
|
+
## Local Images Not Already Under `artifacts/`
|
|
58
|
+
|
|
59
|
+
If you want to embed a local image that lives **outside** `artifacts/` — e.g. a file the user pasted into `data/attachments/2026/04/foo.png`, a wiki source under `data/wiki/sources/foo.png`, or any other workspace path — **do not link to it directly**. The three-`../` math only resolves cleanly for files under `artifacts/`; references that climb further or sideways break under `file://` and are fragile across workspace reorganisation.
|
|
60
|
+
|
|
61
|
+
Copy the file into `artifacts/images/<YYYY>/<MM>/` first, then reference the copy. Use `mkdir -p` for the partition directory and a UTC year/month matching the convention (`saveImage()` shards by UTC, so doing the same keeps copies grouped with same-month generated images):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
mkdir -p artifacts/images/2026/04
|
|
65
|
+
cp data/attachments/2026/04/foo.png artifacts/images/2026/04/foo.png
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then in the HTML:
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<img src="../../../images/2026/04/foo.png">
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Files **already** under `artifacts/` (e.g. `artifacts/images/2026/03/bar.png`, `artifacts/charts/2026/04/baz.svg`) — reference them in place; do **not** copy.
|
|
75
|
+
|
|
76
|
+
## Why Three `../`
|
|
77
|
+
|
|
78
|
+
The file is saved at `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html`. From that location, `../../../` climbs out of `<MM>/`, `<YYYY>/`, and `html/` to land in `artifacts/`. From `artifacts/`, the next path segment is the sibling artifact directory you want — `images/`, `charts/`, `documents/`, etc.
|
|
79
|
+
|
|
80
|
+
Absolute paths like `/artifacts/...` work in-app (the server mounts `/artifacts/images` as a static route) but break under `file://`, where root-relative URLs resolve against the filesystem root. Workspace-rooted paths without a leading slash (`artifacts/...`) get joined to the page URL by the browser and 404 every time.
|
|
@@ -25,6 +25,13 @@ The container runs with `--cap-drop ALL` and as the host user's UID/GID, so it h
|
|
|
25
25
|
|
|
26
26
|
Set the environment variable `DISABLE_SANDBOX=1` to always run the agent directly on the host, even when Docker is available.
|
|
27
27
|
|
|
28
|
+
## Debug aids (opt-in env vars)
|
|
29
|
+
|
|
30
|
+
These flags exist for development / debugging only. Off by default so production runs aren't surprised.
|
|
31
|
+
|
|
32
|
+
- `DISABLE_SANDBOX=1` — see above. Bypasses the Docker sandbox.
|
|
33
|
+
- `PERSIST_TOOL_CALLS=1` — also persist `tool_call` events to the session jsonl alongside `tool_result`. Useful for reading the args sent to a tool after the run is over (page refresh / server restart). Off by default because args can be large and may carry payload bytes (inline images, full MulmoScript JSON) you didn't expect to land in the jsonl. See [issue #1096](https://github.com/receptron/mulmoclaude/issues/1096) for the rationale.
|
|
34
|
+
|
|
28
35
|
## Host Credentials (opt-in)
|
|
29
36
|
|
|
30
37
|
The sandbox is credential-free by default. Two opt-in flags let you expose the minimum needed for `git` / `gh` to authenticate without leaking private keys into the container:
|