mulmoclaude 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Translation service factory. Reads the on-disk cache, routes
|
|
2
|
+
// only the missing sentences through an injected `translateBatch`,
|
|
3
|
+
// merges new translations back, and returns the assembled result
|
|
4
|
+
// in the caller's input order.
|
|
5
|
+
//
|
|
6
|
+
// The injection seam is deliberate: production wires
|
|
7
|
+
// `defaultTranslateBatch` from `./llm.ts` (which spawns the
|
|
8
|
+
// `claude` CLI), unit tests pass a deterministic fake.
|
|
9
|
+
|
|
10
|
+
import { loadDictionary, saveDictionary } from "../../utils/files/translation-io.js";
|
|
11
|
+
import { assembleResult, mergeTranslations, splitHitMiss } from "./cache.js";
|
|
12
|
+
import type { TranslateRequest, TranslateResponse, TranslationService, TranslationServiceDeps } from "./types.js";
|
|
13
|
+
|
|
14
|
+
const NAMESPACE_RE = /^[a-zA-Z0-9_-]+$/;
|
|
15
|
+
// Fixed-length alternation, no nested quantifiers — safe from ReDoS.
|
|
16
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- single-pass match against a 2- or 5-char locale code, no backtracking.
|
|
17
|
+
const LANGUAGE_RE = /^[a-z]{2}(?:-[A-Z]{2})?$/;
|
|
18
|
+
|
|
19
|
+
// Bound the request shape so a single call cannot blow past the
|
|
20
|
+
// `claude -p <json>` argv limit (POSIX `E2BIG`, typically ~128 KiB)
|
|
21
|
+
// or balloon the per-call cost. UI-string callers stay well inside
|
|
22
|
+
// these — Role suggested queries are ~5 strings × ~50 chars.
|
|
23
|
+
const MAX_SENTENCES = 256;
|
|
24
|
+
const MAX_SENTENCE_CHARS = 1024;
|
|
25
|
+
const MAX_TOTAL_CHARS = 32 * 1024;
|
|
26
|
+
|
|
27
|
+
export class TranslationInputError extends Error {
|
|
28
|
+
constructor(message: string) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "TranslationInputError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validateRequest(req: TranslateRequest): void {
|
|
35
|
+
if (typeof req.namespace !== "string" || !NAMESPACE_RE.test(req.namespace)) {
|
|
36
|
+
throw new TranslationInputError(`invalid namespace: ${JSON.stringify(req.namespace)}`);
|
|
37
|
+
}
|
|
38
|
+
if (typeof req.targetLanguage !== "string" || !LANGUAGE_RE.test(req.targetLanguage)) {
|
|
39
|
+
throw new TranslationInputError(`invalid targetLanguage: ${JSON.stringify(req.targetLanguage)}`);
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(req.sentences) || req.sentences.length === 0) {
|
|
42
|
+
throw new TranslationInputError("sentences must be a non-empty array");
|
|
43
|
+
}
|
|
44
|
+
if (req.sentences.length > MAX_SENTENCES) {
|
|
45
|
+
throw new TranslationInputError(`sentences exceeds ${MAX_SENTENCES} entries`);
|
|
46
|
+
}
|
|
47
|
+
let totalChars = 0;
|
|
48
|
+
for (const sentence of req.sentences) {
|
|
49
|
+
if (typeof sentence !== "string" || sentence.length === 0) {
|
|
50
|
+
throw new TranslationInputError("sentences must contain non-empty strings");
|
|
51
|
+
}
|
|
52
|
+
if (sentence.length > MAX_SENTENCE_CHARS) {
|
|
53
|
+
throw new TranslationInputError(`sentence exceeds ${MAX_SENTENCE_CHARS} characters`);
|
|
54
|
+
}
|
|
55
|
+
totalChars += sentence.length;
|
|
56
|
+
if (totalChars > MAX_TOTAL_CHARS) {
|
|
57
|
+
throw new TranslationInputError(`total sentence length exceeds ${MAX_TOTAL_CHARS} characters`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createTranslationService(deps: TranslationServiceDeps): TranslationService {
|
|
63
|
+
const { translateBatch, workspaceRoot } = deps;
|
|
64
|
+
// Per-namespace serialization chain. Two concurrent translate() calls
|
|
65
|
+
// on the same namespace would otherwise race the read-merge-write
|
|
66
|
+
// step; chaining ensures the second sees the first's persisted output.
|
|
67
|
+
const chains = new Map<string, Promise<unknown>>();
|
|
68
|
+
|
|
69
|
+
async function runOnce(req: TranslateRequest): Promise<TranslateResponse> {
|
|
70
|
+
const dict = loadDictionary(req.namespace, workspaceRoot);
|
|
71
|
+
const { cached, misses } = splitHitMiss(dict, req.sentences, req.targetLanguage);
|
|
72
|
+
if (misses.length === 0) {
|
|
73
|
+
return { translations: assembleResult(req.sentences, cached, new Map()) };
|
|
74
|
+
}
|
|
75
|
+
const translated = await translateBatch({ targetLanguage: req.targetLanguage, sentences: misses });
|
|
76
|
+
if (translated.length !== misses.length) {
|
|
77
|
+
throw new Error(`[translation] translateBatch returned ${translated.length} translations for ${misses.length} sentences`);
|
|
78
|
+
}
|
|
79
|
+
const fresh = new Map<string, string>();
|
|
80
|
+
misses.forEach((sentence, index) => fresh.set(sentence, translated[index]));
|
|
81
|
+
const next = mergeTranslations(dict, req.targetLanguage, fresh);
|
|
82
|
+
await saveDictionary(req.namespace, next, workspaceRoot);
|
|
83
|
+
return { translations: assembleResult(req.sentences, cached, fresh) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function serialize<T>(namespace: string, runner: () => Promise<T>): Promise<T> {
|
|
87
|
+
const prev = chains.get(namespace) ?? Promise.resolve();
|
|
88
|
+
const next = prev.catch(() => undefined).then(runner);
|
|
89
|
+
const tracked = next.catch(() => undefined);
|
|
90
|
+
chains.set(namespace, tracked);
|
|
91
|
+
tracked.then(() => {
|
|
92
|
+
if (chains.get(namespace) === tracked) chains.delete(namespace);
|
|
93
|
+
});
|
|
94
|
+
return next;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function translate(req: TranslateRequest): Promise<TranslateResponse> {
|
|
98
|
+
validateRequest(req);
|
|
99
|
+
if (req.targetLanguage === "en") {
|
|
100
|
+
return { translations: [...req.sentences] };
|
|
101
|
+
}
|
|
102
|
+
return serialize(req.namespace, () => runOnce(req));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { translate };
|
|
106
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Production `translateBatch`: spawns the `claude` CLI in print
|
|
2
|
+
// mode with a JSON schema so the response array length is
|
|
3
|
+
// guaranteed to match the request. Same auth model as the rest of
|
|
4
|
+
// the server (no API key required).
|
|
5
|
+
//
|
|
6
|
+
// Patterned after `server/workspace/chat-index/summarizer.ts`.
|
|
7
|
+
|
|
8
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { CLI_SUBPROCESS_TIMEOUT_MS } from "../../utils/time.js";
|
|
11
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
12
|
+
import { formatSpawnFailure } from "../../utils/spawn.js";
|
|
13
|
+
import { isRecord } from "../../utils/types.js";
|
|
14
|
+
import { ClaudeCliNotFoundError } from "../../workspace/journal/archivist-cli.js";
|
|
15
|
+
import type { TranslateBatchFn } from "./types.js";
|
|
16
|
+
|
|
17
|
+
const SYSTEM_PROMPT =
|
|
18
|
+
"You are a translation engine. The user input is a JSON object with `targetLanguage` (BCP-47) " +
|
|
19
|
+
"and `sentences` (an array of English source strings). Translate each sentence into the target " +
|
|
20
|
+
"language and return strict JSON matching the provided schema. The output `translations` array " +
|
|
21
|
+
"MUST have the same length and order as the input `sentences` array. Preserve placeholders " +
|
|
22
|
+
"such as `{name}`, `{count}`, `%s`, and HTML tags verbatim.";
|
|
23
|
+
|
|
24
|
+
const SCHEMA = {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
translations: {
|
|
28
|
+
type: "array",
|
|
29
|
+
items: { type: "string" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ["translations"],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Small per-call cap. A few dozen short UI strings on haiku costs
|
|
36
|
+
// fractions of a cent; this guards against runaway prompts only.
|
|
37
|
+
const MAX_BUDGET_USD = 0.5;
|
|
38
|
+
|
|
39
|
+
interface ClaudeJsonEnvelope {
|
|
40
|
+
is_error?: boolean;
|
|
41
|
+
result?: string;
|
|
42
|
+
structured_output?: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function parseTranslations(stdout: string): string[] {
|
|
46
|
+
let parsed: ClaudeJsonEnvelope;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(stdout.trim());
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(`[translation] failed to parse claude json output: ${errorMessage(err)}`);
|
|
51
|
+
}
|
|
52
|
+
if (parsed.is_error) {
|
|
53
|
+
throw new Error(`[translation] claude returned error: ${parsed.result ?? "unknown"}`);
|
|
54
|
+
}
|
|
55
|
+
if (!isRecord(parsed.structured_output)) {
|
|
56
|
+
throw new Error("[translation] structured_output missing or not an object");
|
|
57
|
+
}
|
|
58
|
+
const { translations } = parsed.structured_output as Record<string, unknown>;
|
|
59
|
+
if (!Array.isArray(translations) || !translations.every((value): value is string => typeof value === "string")) {
|
|
60
|
+
throw new Error("[translation] translations is not a string array");
|
|
61
|
+
}
|
|
62
|
+
return translations;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildArgs(promptInput: string): string[] {
|
|
66
|
+
return [
|
|
67
|
+
"--print",
|
|
68
|
+
"--no-session-persistence",
|
|
69
|
+
"--output-format",
|
|
70
|
+
"json",
|
|
71
|
+
"--model",
|
|
72
|
+
"haiku",
|
|
73
|
+
"--max-budget-usd",
|
|
74
|
+
String(MAX_BUDGET_USD),
|
|
75
|
+
"--json-schema",
|
|
76
|
+
JSON.stringify(SCHEMA),
|
|
77
|
+
"--system-prompt",
|
|
78
|
+
SYSTEM_PROMPT,
|
|
79
|
+
"-p",
|
|
80
|
+
promptInput,
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface SpawnState {
|
|
85
|
+
stdout: string;
|
|
86
|
+
stderr: string;
|
|
87
|
+
settled: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type RejectFn = (err: Error) => void;
|
|
91
|
+
type ResolveFn = (value: string) => void;
|
|
92
|
+
|
|
93
|
+
function onTimeout(state: SpawnState, proc: ChildProcess, reject: RejectFn, timeoutMs: number): void {
|
|
94
|
+
if (state.settled) return;
|
|
95
|
+
state.settled = true;
|
|
96
|
+
proc.kill("SIGKILL");
|
|
97
|
+
reject(new Error(`[translation] claude translate timed out after ${timeoutMs}ms`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onError(state: SpawnState, timer: ReturnType<typeof setTimeout>, err: Error & { code?: string }, reject: RejectFn): void {
|
|
101
|
+
if (state.settled) return;
|
|
102
|
+
state.settled = true;
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
reject(err.code === "ENOENT" ? new ClaudeCliNotFoundError() : err);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function onClose(state: SpawnState, timer: ReturnType<typeof setTimeout>, code: number | null, resolve: ResolveFn, reject: RejectFn): void {
|
|
108
|
+
if (state.settled) return;
|
|
109
|
+
state.settled = true;
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
if (code !== 0) {
|
|
112
|
+
reject(new Error(formatSpawnFailure("[translation]", code, state.stdout, state.stderr)));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
resolve(state.stdout);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function spawnClaudeTranslate(promptInput: string, timeoutMs: number): Promise<string> {
|
|
119
|
+
return new Promise<string>((resolve, reject) => {
|
|
120
|
+
// Run from tmpdir so claude does not load the project's
|
|
121
|
+
// CLAUDE.md / plugins / memory and inflate the context.
|
|
122
|
+
const proc = spawn("claude", buildArgs(promptInput), { cwd: tmpdir(), stdio: ["ignore", "pipe", "pipe"] });
|
|
123
|
+
const state: SpawnState = { stdout: "", stderr: "", settled: false };
|
|
124
|
+
const timer = setTimeout(() => onTimeout(state, proc, reject, timeoutMs), timeoutMs);
|
|
125
|
+
proc.stdout.on("data", (chunk: Buffer) => {
|
|
126
|
+
state.stdout += chunk.toString();
|
|
127
|
+
});
|
|
128
|
+
proc.stderr.on("data", (chunk: Buffer) => {
|
|
129
|
+
state.stderr += chunk.toString();
|
|
130
|
+
});
|
|
131
|
+
proc.on("error", (err: Error & { code?: string }) => onError(state, timer, err, reject));
|
|
132
|
+
proc.on("close", (code) => onClose(state, timer, code, resolve, reject));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const defaultTranslateBatch: TranslateBatchFn = async ({ targetLanguage, sentences }) => {
|
|
137
|
+
const promptInput = JSON.stringify({ targetLanguage, sentences });
|
|
138
|
+
const stdout = await spawnClaudeTranslate(promptInput, CLI_SUBPROCESS_TIMEOUT_MS);
|
|
139
|
+
return parseTranslations(stdout);
|
|
140
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Public types for the translation service. The on-disk dictionary
|
|
2
|
+
// shape is the authoritative cache format — see plans/done/feat-translation-service.md.
|
|
3
|
+
|
|
4
|
+
export interface TranslateRequest {
|
|
5
|
+
/** Dictionary namespace; also the cache filename. */
|
|
6
|
+
readonly namespace: string;
|
|
7
|
+
/** BCP-47 short code, e.g. `ja`, `pt-BR`. */
|
|
8
|
+
readonly targetLanguage: string;
|
|
9
|
+
/** English source sentences in the order the caller wants them returned. */
|
|
10
|
+
readonly sentences: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TranslateResponse {
|
|
14
|
+
readonly translations: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DictionaryFile {
|
|
18
|
+
readonly sentences: Record<string, Record<string, string>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TranslateBatchInput {
|
|
22
|
+
readonly targetLanguage: string;
|
|
23
|
+
readonly sentences: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TranslateBatchFn = (input: TranslateBatchInput) => Promise<string[]>;
|
|
27
|
+
|
|
28
|
+
export interface TranslationServiceDeps {
|
|
29
|
+
readonly translateBatch: TranslateBatchFn;
|
|
30
|
+
readonly workspaceRoot?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TranslationService {
|
|
34
|
+
readonly translate: (req: TranslateRequest) => Promise<TranslateResponse>;
|
|
35
|
+
}
|
|
@@ -85,6 +85,17 @@ async function renewTokenViaPty(): Promise<boolean> {
|
|
|
85
85
|
let responded = false;
|
|
86
86
|
let buffer = "";
|
|
87
87
|
let settled = false;
|
|
88
|
+
// Mutual reference: `finish`'s body needs `timeout` (clearTimeout)
|
|
89
|
+
// and `timeout`'s callback needs `finish`. Predeclared with `let`
|
|
90
|
+
// and assigned exactly once below. `prefer-const` would prefer a
|
|
91
|
+
// direct `const timeout = setTimeout(...)` form, but that needs
|
|
92
|
+
// `finish` already in scope inside the callback, which then
|
|
93
|
+
// forces `clearTimeout(timeout)` inside `finish`'s body to
|
|
94
|
+
// reference an undefined-at-textual-position const — i.e. the
|
|
95
|
+
// chicken-and-egg pair has no const-only spelling. The actual
|
|
96
|
+
// value is single-write at runtime; lint heuristic disagrees.
|
|
97
|
+
// eslint-disable-next-line prefer-const -- mutual-reference pair, see comment above
|
|
98
|
+
let timeout: ReturnType<typeof setTimeout>;
|
|
88
99
|
|
|
89
100
|
const finish = (success: boolean) => {
|
|
90
101
|
if (settled) return;
|
|
@@ -94,7 +105,7 @@ async function renewTokenViaPty(): Promise<boolean> {
|
|
|
94
105
|
resolve(success);
|
|
95
106
|
};
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
timeout = setTimeout(() => {
|
|
98
109
|
log.error("credentials", `Token renewal timed out after ${PTY_TIMEOUT_MS / ONE_SECOND_MS}s`);
|
|
99
110
|
finish(false);
|
|
100
111
|
}, PTY_TIMEOUT_MS);
|
|
@@ -210,7 +221,7 @@ export async function refreshCredentials(): Promise<boolean> {
|
|
|
210
221
|
|
|
211
222
|
// Atomic so a readers mid-refresh can't see a truncated creds
|
|
212
223
|
// file; mode preserves the 0o600 we always set on this file.
|
|
213
|
-
await writeFileAtomic(CREDENTIALS_PATH, credentials
|
|
224
|
+
await writeFileAtomic(CREDENTIALS_PATH, `${credentials}\n`, { mode: 0o600 });
|
|
214
225
|
log.info("credentials", "Fresh credentials written to ~/.claude/.credentials.json");
|
|
215
226
|
return true;
|
|
216
227
|
} catch (err) {
|
package/server/system/env.ts
CHANGED
|
@@ -58,6 +58,12 @@ export const env = Object.freeze({
|
|
|
58
58
|
|
|
59
59
|
// Sandbox / Docker
|
|
60
60
|
disableSandbox: asFlag(process.env.DISABLE_SANDBOX),
|
|
61
|
+
// Debug aid: also persist `tool_call` events to the session
|
|
62
|
+
// jsonl (the `tool_result` side already lands on disk). Off by
|
|
63
|
+
// default because args can be large and may carry payload bytes
|
|
64
|
+
// the user didn't expect to land in this exact form. See
|
|
65
|
+
// plans/done/feat-persist-tool-calls.md / issue #1096.
|
|
66
|
+
persistToolCalls: asFlag(process.env.PERSIST_TOOL_CALLS),
|
|
61
67
|
// Host-credential opt-ins for the Docker sandbox (#259). Both off
|
|
62
68
|
// by default. See docs/sandbox-credentials.md for the contract.
|
|
63
69
|
sandboxSshAgentForward: asFlag(process.env.SANDBOX_SSH_AGENT_FORWARD),
|
|
@@ -107,7 +113,6 @@ export const env = Object.freeze({
|
|
|
107
113
|
mcpSessionId: process.env.SESSION_ID ?? "",
|
|
108
114
|
mcpHost: process.env.MCP_HOST ?? "localhost",
|
|
109
115
|
mcpPluginNames: asCsv(process.env.PLUGIN_NAMES),
|
|
110
|
-
mcpRoleIds: asCsv(process.env.ROLE_IDS),
|
|
111
116
|
});
|
|
112
117
|
|
|
113
118
|
// ── Derived helpers ─────────────────────────────────────────────────
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Formatter, LogRecord } from "./types.js";
|
|
1
|
+
import type { Formatter, LogLevel, LogRecord } from "./types.js";
|
|
2
2
|
|
|
3
3
|
function formatData(data: Record<string, unknown> | undefined): string {
|
|
4
4
|
if (!data) return "";
|
|
@@ -23,11 +23,53 @@ function stringifyScalar(value: unknown): string {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// ANSI escape codes — small enough to inline rather than pull a dep.
|
|
27
|
+
// `1m` adds bold so the level word reads at a glance even on busy
|
|
28
|
+
// terminals. The level field stays width-padded with `.padEnd(5)` so
|
|
29
|
+
// the colour escape doesn't push subsequent columns out of alignment.
|
|
30
|
+
const ANSI_RESET = "\x1b[0m";
|
|
31
|
+
const LEVEL_COLOR: Record<LogLevel, string> = {
|
|
32
|
+
error: "\x1b[1;31m", // bold red
|
|
33
|
+
warn: "\x1b[1;33m", // bold yellow
|
|
34
|
+
info: "\x1b[1;36m", // bold cyan
|
|
35
|
+
debug: "\x1b[2;37m", // dim white (close to gray)
|
|
29
36
|
};
|
|
30
37
|
|
|
38
|
+
// Levels for which the colour wraps the entire log line, not just the
|
|
39
|
+
// level word. The intent is that a `tail -f` reader can spot warnings
|
|
40
|
+
// and errors without parsing — the whole row glows red / yellow.
|
|
41
|
+
// Info / debug stay quiet so a chatty server doesn't fill the terminal
|
|
42
|
+
// with cyan or grey.
|
|
43
|
+
const WHOLE_LINE_COLOR: ReadonlySet<LogLevel> = new Set(["error", "warn"]);
|
|
44
|
+
|
|
45
|
+
export const formatText: Formatter = (record: LogRecord): string => formatTextLine(record, false);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Same shape as {@link formatText} but wraps the output in ANSI colour
|
|
49
|
+
* escapes per level. Console sinks select this variant only when
|
|
50
|
+
* stdout/stderr is a real TTY (or `FORCE_COLOR` is set) and `NO_COLOR`
|
|
51
|
+
* is unset — file sinks and CI logs always get the plain
|
|
52
|
+
* {@link formatText}.
|
|
53
|
+
*
|
|
54
|
+
* - `error` / `warn`: the whole line is wrapped (bold red / bold yellow)
|
|
55
|
+
* so the row stands out at a glance.
|
|
56
|
+
* - `info` / `debug`: only the level word is coloured, keeping the
|
|
57
|
+
* chatty levels visually quiet.
|
|
58
|
+
*/
|
|
59
|
+
export const formatTextColor: Formatter = (record: LogRecord): string => formatTextLine(record, true);
|
|
60
|
+
|
|
61
|
+
function formatTextLine(record: LogRecord, color: boolean): string {
|
|
62
|
+
const levelText = record.level.toUpperCase().padEnd(5);
|
|
63
|
+
const colorCode = LEVEL_COLOR[record.level];
|
|
64
|
+
if (color && WHOLE_LINE_COLOR.has(record.level)) {
|
|
65
|
+
// Whole-row wrap. Avoid double-wrapping the level slot — one
|
|
66
|
+
// colour code at the start + reset at the end is enough.
|
|
67
|
+
return `${colorCode}${record.time} ${levelText} [${record.prefix}] ${record.message}${formatData(record.data)}${ANSI_RESET}`;
|
|
68
|
+
}
|
|
69
|
+
const level = color ? `${colorCode}${levelText}${ANSI_RESET}` : levelText;
|
|
70
|
+
return `${record.time} ${level} [${record.prefix}] ${record.message}${formatData(record.data)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
export const formatJson: Formatter = (record: LogRecord): string => {
|
|
32
74
|
const payload: Record<string, unknown> = {
|
|
33
75
|
time: record.time,
|
|
@@ -9,10 +9,10 @@ export type { LoggerConfig } from "./config.js";
|
|
|
9
9
|
export { resolveConfig, DEFAULT_CONFIG } from "./config.js";
|
|
10
10
|
|
|
11
11
|
export interface Logger {
|
|
12
|
-
error(prefix: string, message: string, data?: Record<string, unknown>)
|
|
13
|
-
warn(prefix: string, message: string, data?: Record<string, unknown>)
|
|
14
|
-
info(prefix: string, message: string, data?: Record<string, unknown>)
|
|
15
|
-
debug(prefix: string, message: string, data?: Record<string, unknown>)
|
|
12
|
+
error: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
13
|
+
warn: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
14
|
+
info: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
15
|
+
debug: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function createLogger(config: LoggerConfig): Logger {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFile } from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import type { ConsoleSinkConfig, FileSinkConfig, TelemetrySinkConfig } from "./config.js";
|
|
4
|
-
import { formatJson, formatText } from "./formatters.js";
|
|
4
|
+
import { formatJson, formatText, formatTextColor } from "./formatters.js";
|
|
5
5
|
import { dailyFileName, enforceMaxFiles, ensureDir } from "./rotation.js";
|
|
6
6
|
import type { Formatter, LogRecord, Sink } from "./types.js";
|
|
7
7
|
|
|
@@ -9,15 +9,36 @@ function pickFormatter(kind: "text" | "json"): Formatter {
|
|
|
9
9
|
return kind === "json" ? formatJson : formatText;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// Decide whether to emit ANSI escapes on this stream:
|
|
13
|
+
// 1. `NO_COLOR=<anything>` → off, always (https://no-color.org/).
|
|
14
|
+
// 2. `FORCE_COLOR=<non-zero>` → on, even when the stream isn't a
|
|
15
|
+
// TTY. Concurrently / yarn / npm scripts set this on child
|
|
16
|
+
// processes by default, so `yarn dev` piped output keeps colour.
|
|
17
|
+
// `FORCE_COLOR=0` is treated as "explicitly off".
|
|
18
|
+
// 3. Otherwise: on iff the stream itself is a real TTY.
|
|
19
|
+
function colorEnabled(stream: { isTTY?: boolean }): boolean {
|
|
20
|
+
if (process.env.NO_COLOR && process.env.NO_COLOR.length > 0) return false;
|
|
21
|
+
const force = process.env.FORCE_COLOR;
|
|
22
|
+
if (force !== undefined && force !== "" && force !== "0" && force !== "false") return true;
|
|
23
|
+
if (force === "0" || force === "false") return false;
|
|
24
|
+
return stream.isTTY === true;
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
export function createConsoleSink(config: ConsoleSinkConfig): Sink {
|
|
13
|
-
|
|
28
|
+
// Pre-resolve a (plain, colour) pair once. `text` may upgrade to the
|
|
29
|
+
// ANSI variant per record based on which stream the record will hit;
|
|
30
|
+
// `json` stays uncoloured so structured-log consumers get a clean
|
|
31
|
+
// stream regardless of the terminal.
|
|
32
|
+
const plain = pickFormatter(config.format);
|
|
33
|
+
const colored: Formatter = config.format === "text" ? formatTextColor : plain;
|
|
34
|
+
|
|
14
35
|
return {
|
|
15
36
|
name: "console",
|
|
16
37
|
level: config.level,
|
|
17
38
|
write(record: LogRecord) {
|
|
18
|
-
const line = fmt(record) + "\n";
|
|
19
39
|
const stream = record.level === "error" || record.level === "warn" ? process.stderr : process.stdout;
|
|
20
|
-
stream
|
|
40
|
+
const fmt = colorEnabled(stream) ? colored : plain;
|
|
41
|
+
stream.write(`${fmt(record)}\n`);
|
|
21
42
|
},
|
|
22
43
|
};
|
|
23
44
|
}
|
|
@@ -72,7 +93,7 @@ export function createFileSink(config: FileSinkConfig, deps: FileSinkDeps = {}):
|
|
|
72
93
|
write(record: LogRecord) {
|
|
73
94
|
const nowDate = now();
|
|
74
95
|
const rotated = maybeRotate(nowDate);
|
|
75
|
-
const line = fmt(record)
|
|
96
|
+
const line = `${fmt(record)}\n`;
|
|
76
97
|
const filePath = currentPath;
|
|
77
98
|
enqueue(() => writeLine(filePath, line));
|
|
78
99
|
// Enforce retention AFTER the write so maxFiles counts include
|
|
@@ -21,9 +21,9 @@ export interface LogRecord {
|
|
|
21
21
|
export interface Sink {
|
|
22
22
|
name: string;
|
|
23
23
|
level: LogLevel;
|
|
24
|
-
write(record: LogRecord)
|
|
24
|
+
write: (record: LogRecord) => void;
|
|
25
25
|
// Drains any pending async I/O. Tests call this; production can ignore.
|
|
26
|
-
flush
|
|
26
|
+
flush?: () => Promise<void>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export type Formatter = (record: LogRecord) => string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Type declarations for dev-plugin-args.mjs. See the .mjs file for
|
|
2
|
+
// rationale on why the shared helper lives in plain JS.
|
|
3
|
+
|
|
4
|
+
export interface DevPluginArg {
|
|
5
|
+
rawInput: string;
|
|
6
|
+
absPath: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ParseDevPluginArgsResult = { ok: true; resolved: DevPluginArg[] } | { ok: false; reason: string };
|
|
10
|
+
|
|
11
|
+
export function parseDevPluginArgs(argv: readonly string[], cwd: string): ParseDevPluginArgsResult;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Shared `--dev-plugin` argv parser for the npm launcher
|
|
2
|
+
// (`packages/mulmoclaude/bin/mulmoclaude.js`) and any other Node entry
|
|
3
|
+
// point that needs to interpret the same flags.
|
|
4
|
+
//
|
|
5
|
+
// Kept as plain `.mjs` for the same reason as `port.mjs`: the launcher
|
|
6
|
+
// runs BEFORE tsx is wired up, so it can't import from a `.ts` file.
|
|
7
|
+
// Sibling `dev-plugin-args.d.mts` carries the type declarations.
|
|
8
|
+
//
|
|
9
|
+
// The parser is pure — no `process.cwd()`, no `console.*`, no
|
|
10
|
+
// `process.exit()`. The caller passes argv + cwd in, and decides what
|
|
11
|
+
// to log and when to exit. That keeps it unit-testable from
|
|
12
|
+
// node:test without spawning subprocesses.
|
|
13
|
+
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
/** Parse repeated `--dev-plugin <path>` flags out of argv.
|
|
17
|
+
*
|
|
18
|
+
* Returns one of:
|
|
19
|
+
* { ok: true, resolved: [{ rawInput, absPath }] }
|
|
20
|
+
* { ok: false, reason }
|
|
21
|
+
*
|
|
22
|
+
* Errors are surfaced as a single human-readable string so the caller
|
|
23
|
+
* can write it to stderr. The launcher's invariants are:
|
|
24
|
+
* - missing argument value (`--dev-plugin` at end of argv, or
|
|
25
|
+
* followed by another `-`-prefixed flag) → reject
|
|
26
|
+
* - relative paths resolve against `cwd`
|
|
27
|
+
* - duplicate flags are accumulated in argv order; the caller
|
|
28
|
+
* detects same-name collisions later, against the resolved package
|
|
29
|
+
* names from disk.
|
|
30
|
+
*/
|
|
31
|
+
export function parseDevPluginArgs(argv, cwd) {
|
|
32
|
+
const resolved = [];
|
|
33
|
+
for (let i = 0; i < argv.length; i++) {
|
|
34
|
+
if (argv[i] !== "--dev-plugin") continue;
|
|
35
|
+
const raw = argv[i + 1];
|
|
36
|
+
if (raw === undefined || raw.startsWith("-")) {
|
|
37
|
+
return { ok: false, reason: "--dev-plugin requires a path argument" };
|
|
38
|
+
}
|
|
39
|
+
resolved.push({ rawInput: raw, absPath: path.resolve(cwd, raw) });
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
return { ok: true, resolved };
|
|
43
|
+
}
|
package/server/utils/errors.ts
CHANGED
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
// `err instanceof Error ? err.message : String(err)` — searching for
|
|
3
3
|
// one canonical helper is easier than grepping for the inline form.
|
|
4
4
|
//
|
|
5
|
+
// Non-Error objects with a `details` (gRPC convention) or `message`
|
|
6
|
+
// string field have that field surfaced — without this, gRPC errors
|
|
7
|
+
// like `{ code, details, metadata }` show up to users as
|
|
8
|
+
// `[object Object]`.
|
|
9
|
+
//
|
|
5
10
|
// The optional `fallback` covers the common route-handler idiom where
|
|
6
11
|
// a throw of a plain non-Error value should surface as a descriptive
|
|
7
|
-
// message ("rebuild failed") rather than `String(err)` noise
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// fine.
|
|
12
|
+
// message ("rebuild failed") rather than `String(err)` noise. Prefer
|
|
13
|
+
// passing a fallback at error-response boundaries — omit it for
|
|
14
|
+
// logging contexts where `String(err)` is fine.
|
|
11
15
|
|
|
12
16
|
export function errorMessage(err: unknown, fallback?: string): string {
|
|
13
17
|
if (err instanceof Error) return err.message;
|
|
18
|
+
if (err !== null && typeof err === "object") {
|
|
19
|
+
const obj = err as { details?: unknown; message?: unknown };
|
|
20
|
+
if (typeof obj.details === "string" && obj.details) return obj.details;
|
|
21
|
+
if (typeof obj.message === "string" && obj.message) return obj.message;
|
|
22
|
+
}
|
|
14
23
|
if (fallback !== undefined) return fallback;
|
|
15
24
|
return String(err);
|
|
16
25
|
}
|