mulmoclaude 0.1.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/README.md +44 -0
- package/bin/mulmoclaude.js +202 -0
- package/bin/prepare-dist.js +93 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +1 -0
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +5 -0
- package/client/assets/index-D8rhwXLq.js +4906 -0
- package/client/assets/index-KNLBjwuh.css +1 -0
- package/client/assets/index.es-D4YyL_Dg-BfRHLTZV.js +5 -0
- package/client/assets/material-icons-Dr0goTwe.woff +0 -0
- package/client/assets/material-icons-kAwBdRge.woff2 +0 -0
- package/client/assets/material-icons-outlined-BpWbwl2n.woff +0 -0
- package/client/assets/material-icons-outlined-DZhiGvEA.woff2 +0 -0
- package/client/assets/material-icons-round-BDlwx-sv.woff +0 -0
- package/client/assets/material-icons-round-DrirKXBx.woff2 +0 -0
- package/client/assets/material-icons-sharp-CH1KkVu7.woff +0 -0
- package/client/assets/material-icons-sharp-gidztirS.woff2 +0 -0
- package/client/assets/material-icons-two-tone-B7wz7mED.woff +0 -0
- package/client/assets/material-icons-two-tone-DuNIpaEj.woff2 +0 -0
- package/client/assets/mulmo_bw-ERmkSv0a.png +0 -0
- package/client/assets/purify.es-Fx1Nqyry-PeS5RUhs.js +2 -0
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +1 -0
- package/client/index.html +28 -0
- package/package.json +66 -0
- package/server/agent/attachmentConverter.ts +270 -0
- package/server/agent/config.ts +414 -0
- package/server/agent/index.ts +260 -0
- package/server/agent/mcp-server.ts +412 -0
- package/server/agent/mcp-tools/index.ts +63 -0
- package/server/agent/mcp-tools/x.ts +188 -0
- package/server/agent/plugin-names.ts +75 -0
- package/server/agent/prompt.ts +349 -0
- package/server/agent/resumeFailover.ts +129 -0
- package/server/agent/sandboxMounts.ts +329 -0
- package/server/agent/stream.ts +194 -0
- package/server/api/auth/bearerAuth.ts +61 -0
- package/server/api/auth/token.ts +98 -0
- package/server/api/csrfGuard.ts +85 -0
- package/server/api/routes/agent.ts +478 -0
- package/server/api/routes/chart.ts +98 -0
- package/server/api/routes/chat-index.ts +46 -0
- package/server/api/routes/config.ts +258 -0
- package/server/api/routes/dispatchResponse.ts +79 -0
- package/server/api/routes/files.ts +812 -0
- package/server/api/routes/html.ts +101 -0
- package/server/api/routes/image.ts +169 -0
- package/server/api/routes/mulmo-script.ts +712 -0
- package/server/api/routes/mulmoScriptValidate.ts +101 -0
- package/server/api/routes/notifications.ts +69 -0
- package/server/api/routes/pdf.ts +163 -0
- package/server/api/routes/plugins.ts +276 -0
- package/server/api/routes/presentHtml.ts +48 -0
- package/server/api/routes/roles.ts +125 -0
- package/server/api/routes/scheduler.ts +153 -0
- package/server/api/routes/schedulerHandlers.ts +151 -0
- package/server/api/routes/schedulerTasks.ts +163 -0
- package/server/api/routes/sessions.ts +294 -0
- package/server/api/routes/sessionsCursor.ts +59 -0
- package/server/api/routes/skills.ts +195 -0
- package/server/api/routes/sources.ts +540 -0
- package/server/api/routes/todos.ts +263 -0
- package/server/api/routes/todosColumnsHandlers.ts +347 -0
- package/server/api/routes/todosHandlers.ts +274 -0
- package/server/api/routes/todosItemsHandlers.ts +386 -0
- package/server/api/routes/wiki/pageIndex.ts +53 -0
- package/server/api/routes/wiki.ts +363 -0
- package/server/api/sandboxStatus.ts +64 -0
- package/server/events/notifications.ts +160 -0
- package/server/events/pub-sub/index.ts +45 -0
- package/server/events/relay-client.ts +288 -0
- package/server/events/scheduler-adapter.ts +302 -0
- package/server/events/session-store/index.ts +492 -0
- package/server/events/task-manager/index.ts +181 -0
- package/server/index.ts +572 -0
- package/server/system/config.ts +243 -0
- package/server/system/credentials.ts +220 -0
- package/server/system/docker.ts +97 -0
- package/server/system/env.ts +109 -0
- package/server/system/logger/config.ts +112 -0
- package/server/system/logger/formatters.ts +40 -0
- package/server/system/logger/index.ts +53 -0
- package/server/system/logger/rotation.ts +37 -0
- package/server/system/logger/sinks.ts +101 -0
- package/server/system/logger/types.ts +29 -0
- package/server/utils/date.ts +57 -0
- package/server/utils/errors.ts +7 -0
- package/server/utils/fetch.ts +27 -0
- package/server/utils/files/atomic.ts +125 -0
- package/server/utils/files/html-io.ts +20 -0
- package/server/utils/files/image-store.ts +66 -0
- package/server/utils/files/index.ts +45 -0
- package/server/utils/files/journal-io.ts +213 -0
- package/server/utils/files/json.ts +69 -0
- package/server/utils/files/markdown-store.ts +33 -0
- package/server/utils/files/naming.ts +50 -0
- package/server/utils/files/reference-dirs-io.ts +45 -0
- package/server/utils/files/roles-io.ts +45 -0
- package/server/utils/files/safe.ts +106 -0
- package/server/utils/files/scheduler-io.ts +20 -0
- package/server/utils/files/scheduler-overrides-io.ts +64 -0
- package/server/utils/files/session-io.ts +136 -0
- package/server/utils/files/spreadsheet-store.ts +63 -0
- package/server/utils/files/todos-io.ts +29 -0
- package/server/utils/files/user-tasks-io.ts +25 -0
- package/server/utils/files/workspace-io.ts +221 -0
- package/server/utils/gemini.ts +59 -0
- package/server/utils/gitignore.ts +69 -0
- package/server/utils/http.ts +15 -0
- package/server/utils/httpError.ts +61 -0
- package/server/utils/id.ts +16 -0
- package/server/utils/json.ts +83 -0
- package/server/utils/logBackgroundError.ts +22 -0
- package/server/utils/markdown.ts +82 -0
- package/server/utils/request.ts +29 -0
- package/server/utils/slug.ts +50 -0
- package/server/utils/spawn.ts +62 -0
- package/server/utils/time.ts +34 -0
- package/server/utils/types.ts +47 -0
- package/server/workspace/chat-index/index.ts +153 -0
- package/server/workspace/chat-index/indexer.ts +209 -0
- package/server/workspace/chat-index/paths.ts +34 -0
- package/server/workspace/chat-index/summarizer.ts +247 -0
- package/server/workspace/chat-index/types.ts +38 -0
- package/server/workspace/custom-dirs.ts +220 -0
- package/server/workspace/helps/business.md +104 -0
- package/server/workspace/helps/github.md +23 -0
- package/server/workspace/helps/index.md +60 -0
- package/server/workspace/helps/mulmoscript.md +249 -0
- package/server/workspace/helps/sandbox.md +90 -0
- package/server/workspace/helps/spreadsheet.md +43 -0
- package/server/workspace/helps/telegram.md +135 -0
- package/server/workspace/helps/wiki.md +131 -0
- package/server/workspace/journal/archivist.ts +386 -0
- package/server/workspace/journal/dailyPass.ts +743 -0
- package/server/workspace/journal/diff.ts +71 -0
- package/server/workspace/journal/index.ts +185 -0
- package/server/workspace/journal/indexFile.ts +136 -0
- package/server/workspace/journal/linkRewrite.ts +4 -0
- package/server/workspace/journal/memoryExtractor.ts +130 -0
- package/server/workspace/journal/optimizationPass.ts +160 -0
- package/server/workspace/journal/paths.ts +76 -0
- package/server/workspace/journal/state.ts +125 -0
- package/server/workspace/paths.ts +158 -0
- package/server/workspace/reference-dirs.ts +252 -0
- package/server/workspace/roles.ts +37 -0
- package/server/workspace/skills/discovery.ts +125 -0
- package/server/workspace/skills/index.ts +10 -0
- package/server/workspace/skills/parser.ts +144 -0
- package/server/workspace/skills/paths.ts +41 -0
- package/server/workspace/skills/scheduler.ts +149 -0
- package/server/workspace/skills/types.ts +30 -0
- package/server/workspace/skills/user-tasks.ts +257 -0
- package/server/workspace/skills/writer.ts +189 -0
- package/server/workspace/sources/arxivDiscovery.ts +182 -0
- package/server/workspace/sources/classifier.ts +268 -0
- package/server/workspace/sources/fetchers/arxiv.ts +170 -0
- package/server/workspace/sources/fetchers/github.ts +106 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +208 -0
- package/server/workspace/sources/fetchers/githubReleases.ts +186 -0
- package/server/workspace/sources/fetchers/index.ts +71 -0
- package/server/workspace/sources/fetchers/registerAll.ts +15 -0
- package/server/workspace/sources/fetchers/rss.ts +141 -0
- package/server/workspace/sources/fetchers/rssParser.ts +295 -0
- package/server/workspace/sources/httpFetcher.ts +230 -0
- package/server/workspace/sources/interests.ts +120 -0
- package/server/workspace/sources/paths.ts +110 -0
- package/server/workspace/sources/pipeline/dedup.ts +60 -0
- package/server/workspace/sources/pipeline/fetch.ts +136 -0
- package/server/workspace/sources/pipeline/index.ts +249 -0
- package/server/workspace/sources/pipeline/notify.ts +72 -0
- package/server/workspace/sources/pipeline/plan.ts +66 -0
- package/server/workspace/sources/pipeline/summarize.ts +189 -0
- package/server/workspace/sources/pipeline/write.ts +185 -0
- package/server/workspace/sources/rateLimiter.ts +148 -0
- package/server/workspace/sources/registry.ts +326 -0
- package/server/workspace/sources/robots.ts +271 -0
- package/server/workspace/sources/sourceState.ts +135 -0
- package/server/workspace/sources/taxonomy.ts +74 -0
- package/server/workspace/sources/types.ts +144 -0
- package/server/workspace/sources/urls.ts +112 -0
- package/server/workspace/tool-trace/classify.ts +114 -0
- package/server/workspace/tool-trace/index.ts +250 -0
- package/server/workspace/tool-trace/writeSearch.ts +98 -0
- package/server/workspace/wiki-backlinks/index.ts +107 -0
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +144 -0
- package/server/workspace/workspace.ts +66 -0
- package/src/App.vue +720 -0
- package/src/assets/mulmo_bw.png +0 -0
- package/src/components/CanvasViewToggle.vue +27 -0
- package/src/components/ChatAttachmentPreview.vue +45 -0
- package/src/components/ChatImagePreview.vue +17 -0
- package/src/components/ChatInput.vue +208 -0
- package/src/components/FileContentHeader.vue +49 -0
- package/src/components/FileContentRenderer.vue +162 -0
- package/src/components/FileTree.vue +115 -0
- package/src/components/FileTreePane.vue +85 -0
- package/src/components/FilesView.vue +206 -0
- package/src/components/LockStatusPopup.vue +111 -0
- package/src/components/NotificationBell.vue +131 -0
- package/src/components/NotificationToast.vue +72 -0
- package/src/components/PluginLauncher.vue +138 -0
- package/src/components/RightSidebar.vue +113 -0
- package/src/components/RoleSelector.vue +64 -0
- package/src/components/SessionHistoryPanel.vue +176 -0
- package/src/components/SessionTabBar.vue +81 -0
- package/src/components/SettingsMcpTab.vue +350 -0
- package/src/components/SettingsModal.vue +275 -0
- package/src/components/SettingsReferenceDirsTab.vue +173 -0
- package/src/components/SettingsWorkspaceDirsTab.vue +174 -0
- package/src/components/SidebarHeader.vue +69 -0
- package/src/components/StackView.vue +360 -0
- package/src/components/SuggestionsPanel.vue +65 -0
- package/src/components/TodoExplorer.vue +358 -0
- package/src/components/ToolResultsPanel.vue +77 -0
- package/src/components/todo/TodoAddDialog.vue +131 -0
- package/src/components/todo/TodoEditDialog.vue +47 -0
- package/src/components/todo/TodoEditPanel.vue +113 -0
- package/src/components/todo/TodoKanbanView.vue +249 -0
- package/src/components/todo/TodoListView.vue +79 -0
- package/src/components/todo/TodoTableView.vue +177 -0
- package/src/composables/useActiveSession.ts +40 -0
- package/src/composables/useAppApi.ts +45 -0
- package/src/composables/useCanvasViewMode.ts +121 -0
- package/src/composables/useChatScroll.ts +47 -0
- package/src/composables/useClickOutside.ts +26 -0
- package/src/composables/useClipboardCopy.ts +44 -0
- package/src/composables/useContentDisplay.ts +52 -0
- package/src/composables/useDebugBeat.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +115 -0
- package/src/composables/useEventListeners.ts +42 -0
- package/src/composables/useExpandedDirs.ts +64 -0
- package/src/composables/useFaviconState.ts +30 -0
- package/src/composables/useFileSelection.ts +115 -0
- package/src/composables/useFileSortMode.ts +24 -0
- package/src/composables/useFileTree.ts +85 -0
- package/src/composables/useFreshPluginData.ts +89 -0
- package/src/composables/useHealth.ts +38 -0
- package/src/composables/useImeAwareEnter.ts +57 -0
- package/src/composables/useKeyNavigation.ts +60 -0
- package/src/composables/useMarkdownLinkHandler.ts +46 -0
- package/src/composables/useMarkdownMode.ts +17 -0
- package/src/composables/useMcpTools.ts +71 -0
- package/src/composables/useMergedSessions.ts +27 -0
- package/src/composables/useNotifications.ts +90 -0
- package/src/composables/usePdfDownload.ts +60 -0
- package/src/composables/usePendingCalls.ts +77 -0
- package/src/composables/usePubSub.ts +85 -0
- package/src/composables/useRightSidebar.ts +23 -0
- package/src/composables/useRoles.ts +34 -0
- package/src/composables/useSandboxStatus.ts +67 -0
- package/src/composables/useSelectedResult.ts +49 -0
- package/src/composables/useSessionDerived.ts +51 -0
- package/src/composables/useSessionHistory.ts +81 -0
- package/src/composables/useSessionSync.ts +57 -0
- package/src/composables/useViewLayout.ts +55 -0
- package/src/config/apiRoutes.ts +173 -0
- package/src/config/pubsubChannels.ts +45 -0
- package/src/config/roles.ts +335 -0
- package/src/config/schedulerActions.ts +25 -0
- package/src/config/toolNames.ts +71 -0
- package/src/config/workspacePaths.ts +24 -0
- package/src/index.css +107 -0
- package/src/main.ts +25 -0
- package/src/plugins/canvas/Preview.vue +13 -0
- package/src/plugins/canvas/View.vue +333 -0
- package/src/plugins/canvas/definition.ts +38 -0
- package/src/plugins/canvas/index.ts +36 -0
- package/src/plugins/chart/Preview.vue +49 -0
- package/src/plugins/chart/View.vue +143 -0
- package/src/plugins/chart/definition.ts +58 -0
- package/src/plugins/chart/index.ts +52 -0
- package/src/plugins/editImage/Preview.vue +13 -0
- package/src/plugins/editImage/View.vue +13 -0
- package/src/plugins/editImage/definition.ts +27 -0
- package/src/plugins/editImage/index.ts +36 -0
- package/src/plugins/generateImage/Preview.vue +13 -0
- package/src/plugins/generateImage/View.vue +33 -0
- package/src/plugins/generateImage/definition.ts +32 -0
- package/src/plugins/generateImage/index.ts +56 -0
- package/src/plugins/manageRoles/Preview.vue +49 -0
- package/src/plugins/manageRoles/View.vue +525 -0
- package/src/plugins/manageRoles/definition.ts +43 -0
- package/src/plugins/manageRoles/index.ts +47 -0
- package/src/plugins/manageSkills/Preview.vue +21 -0
- package/src/plugins/manageSkills/View.vue +321 -0
- package/src/plugins/manageSkills/definition.ts +49 -0
- package/src/plugins/manageSkills/index.ts +49 -0
- package/src/plugins/manageSource/Preview.vue +33 -0
- package/src/plugins/manageSource/View.vue +697 -0
- package/src/plugins/manageSource/definition.ts +63 -0
- package/src/plugins/manageSource/index.ts +66 -0
- package/src/plugins/markdown/Preview.vue +77 -0
- package/src/plugins/markdown/View.vue +476 -0
- package/src/plugins/markdown/definition.ts +50 -0
- package/src/plugins/markdown/index.ts +36 -0
- package/src/plugins/presentHtml/Preview.vue +25 -0
- package/src/plugins/presentHtml/View.vue +52 -0
- package/src/plugins/presentHtml/definition.ts +27 -0
- package/src/plugins/presentHtml/helpers.ts +72 -0
- package/src/plugins/presentHtml/index.ts +41 -0
- package/src/plugins/presentMulmoScript/Preview.vue +23 -0
- package/src/plugins/presentMulmoScript/View.vue +1166 -0
- package/src/plugins/presentMulmoScript/definition.ts +95 -0
- package/src/plugins/presentMulmoScript/helpers.ts +162 -0
- package/src/plugins/presentMulmoScript/index.ts +40 -0
- package/src/plugins/scheduler/Preview.vue +67 -0
- package/src/plugins/scheduler/TasksTab.vue +205 -0
- package/src/plugins/scheduler/View.vue +565 -0
- package/src/plugins/scheduler/definition.ts +57 -0
- package/src/plugins/scheduler/index.ts +45 -0
- package/src/plugins/scheduler/viewModes.ts +26 -0
- package/src/plugins/spreadsheet/Preview.vue +29 -0
- package/src/plugins/spreadsheet/View.vue +997 -0
- package/src/plugins/spreadsheet/cellHighlights.ts +79 -0
- package/src/plugins/spreadsheet/definition.ts +121 -0
- package/src/plugins/spreadsheet/engine/calculator.ts +459 -0
- package/src/plugins/spreadsheet/engine/cellBuilder.ts +81 -0
- package/src/plugins/spreadsheet/engine/date-parser.ts +220 -0
- package/src/plugins/spreadsheet/engine/date-utils.ts +56 -0
- package/src/plugins/spreadsheet/engine/engine.ts +176 -0
- package/src/plugins/spreadsheet/engine/evaluator.ts +390 -0
- package/src/plugins/spreadsheet/engine/formatter.ts +172 -0
- package/src/plugins/spreadsheet/engine/formulaRefs.ts +101 -0
- package/src/plugins/spreadsheet/engine/functions/date.ts +299 -0
- package/src/plugins/spreadsheet/engine/functions/financial.ts +387 -0
- package/src/plugins/spreadsheet/engine/functions/index.ts +16 -0
- package/src/plugins/spreadsheet/engine/functions/logical.ts +262 -0
- package/src/plugins/spreadsheet/engine/functions/lookup.ts +400 -0
- package/src/plugins/spreadsheet/engine/functions/mathematical.ts +297 -0
- package/src/plugins/spreadsheet/engine/functions/statistical.ts +338 -0
- package/src/plugins/spreadsheet/engine/functions/text.ts +389 -0
- package/src/plugins/spreadsheet/engine/index.ts +27 -0
- package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +111 -0
- package/src/plugins/spreadsheet/engine/parser.ts +143 -0
- package/src/plugins/spreadsheet/engine/registry.ts +150 -0
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +67 -0
- package/src/plugins/spreadsheet/engine/types.ts +64 -0
- package/src/plugins/spreadsheet/index.ts +36 -0
- package/src/plugins/textResponse/Preview.vue +94 -0
- package/src/plugins/textResponse/View.vue +503 -0
- package/src/plugins/textResponse/definition.ts +34 -0
- package/src/plugins/textResponse/index.ts +27 -0
- package/src/plugins/textResponse/plugin.ts +29 -0
- package/src/plugins/textResponse/samples.ts +97 -0
- package/src/plugins/textResponse/types.ts +11 -0
- package/src/plugins/todo/Preview.vue +63 -0
- package/src/plugins/todo/View.vue +364 -0
- package/src/plugins/todo/composables/useTodos.ts +177 -0
- package/src/plugins/todo/definition.ts +45 -0
- package/src/plugins/todo/index.ts +61 -0
- package/src/plugins/todo/labels.ts +163 -0
- package/src/plugins/todo/priority.ts +98 -0
- package/src/plugins/todo/viewModes.ts +19 -0
- package/src/plugins/ui-image/ImagePreview.vue +23 -0
- package/src/plugins/ui-image/ImageView.vue +34 -0
- package/src/plugins/ui-image/index.ts +3 -0
- package/src/plugins/ui-image/types.ts +4 -0
- package/src/plugins/wiki/Preview.vue +65 -0
- package/src/plugins/wiki/View.vue +342 -0
- package/src/plugins/wiki/definition.ts +25 -0
- package/src/plugins/wiki/helpers.ts +59 -0
- package/src/plugins/wiki/index.ts +52 -0
- package/src/router/guards.ts +61 -0
- package/src/router/index.ts +50 -0
- package/src/tools/index.ts +52 -0
- package/src/tools/types.ts +27 -0
- package/src/types/events.ts +16 -0
- package/src/types/fileTree.ts +13 -0
- package/src/types/notification.ts +67 -0
- package/src/types/session.ts +116 -0
- package/src/types/sse.ts +90 -0
- package/src/types/toolCallHistory.ts +13 -0
- package/src/utils/agent/eventDispatch.ts +74 -0
- package/src/utils/agent/request.ts +55 -0
- package/src/utils/agent/toolCalls.ts +62 -0
- package/src/utils/api.ts +218 -0
- package/src/utils/canvas/viewMode.ts +46 -0
- package/src/utils/dom/authTokenMeta.ts +20 -0
- package/src/utils/dom/clickOutside.ts +11 -0
- package/src/utils/dom/externalLink.ts +57 -0
- package/src/utils/dom/scrollable.ts +24 -0
- package/src/utils/errors.ts +11 -0
- package/src/utils/files/expandedDirs.ts +25 -0
- package/src/utils/files/filename.ts +12 -0
- package/src/utils/files/sortChildren.ts +20 -0
- package/src/utils/filesPreview/schedulerPreview.ts +38 -0
- package/src/utils/filesPreview/todoPreview.ts +40 -0
- package/src/utils/format/date.ts +85 -0
- package/src/utils/format/frontmatter.ts +80 -0
- package/src/utils/format/jsonSyntax.ts +109 -0
- package/src/utils/html/previewCsp.ts +65 -0
- package/src/utils/image/resolve.ts +8 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +182 -0
- package/src/utils/markdown/extractFirstH1.ts +39 -0
- package/src/utils/notification/dispatch.ts +22 -0
- package/src/utils/path/relativeLink.ts +130 -0
- package/src/utils/role/icon.ts +20 -0
- package/src/utils/role/merge.ts +10 -0
- package/src/utils/role/plugins.ts +12 -0
- package/src/utils/session/mergeSessions.ts +103 -0
- package/src/utils/session/seedRoleDefault.ts +35 -0
- package/src/utils/session/sessionEntries.ts +121 -0
- package/src/utils/session/sessionFactory.ts +22 -0
- package/src/utils/session/sessionHelpers.ts +99 -0
- package/src/utils/tools/dedup.ts +17 -0
- package/src/utils/tools/mcp.ts +33 -0
- package/src/utils/tools/pendingCalls.ts +16 -0
- package/src/utils/tools/result.ts +40 -0
- package/src/utils/types.ts +44 -0
package/server/index.ts
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import express, { Request, Response, NextFunction } from "express";
|
|
3
|
+
import net from "net";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import agentRoutes from "./api/routes/agent.js";
|
|
7
|
+
import todosRoutes from "./api/routes/todos.js";
|
|
8
|
+
import schedulerRoutes from "./api/routes/scheduler.js";
|
|
9
|
+
import sessionsRoutes from "./api/routes/sessions.js";
|
|
10
|
+
import chatIndexRoutes from "./api/routes/chat-index.js";
|
|
11
|
+
import sourcesRoutes from "./api/routes/sources.js";
|
|
12
|
+
import pluginsRoutes from "./api/routes/plugins.js";
|
|
13
|
+
import imageRoutes from "./api/routes/image.js";
|
|
14
|
+
import presentHtmlRoutes from "./api/routes/presentHtml.js";
|
|
15
|
+
import chartRoutes from "./api/routes/chart.js";
|
|
16
|
+
import rolesRoutes from "./api/routes/roles.js";
|
|
17
|
+
import { DEFAULT_ROLE_ID } from "../src/config/roles.js";
|
|
18
|
+
import mulmoScriptRoutes from "./api/routes/mulmo-script.js";
|
|
19
|
+
import wikiRoutes from "./api/routes/wiki.js";
|
|
20
|
+
import pdfRoutes from "./api/routes/pdf.js";
|
|
21
|
+
import filesRoutes from "./api/routes/files.js";
|
|
22
|
+
import configRoutes from "./api/routes/config.js";
|
|
23
|
+
import skillsRoutes from "./api/routes/skills.js";
|
|
24
|
+
import { createNotificationsRouter } from "./api/routes/notifications.js";
|
|
25
|
+
import { type NotificationDeps, initNotifications } from "./events/notifications.js";
|
|
26
|
+
import { createChatService } from "@mulmobridge/chat-service";
|
|
27
|
+
import { loadAllSessions } from "./api/routes/sessions.js";
|
|
28
|
+
import { readSessionJsonl } from "./utils/files/session-io.js";
|
|
29
|
+
import { onSessionEvent } from "./events/session-store/index.js";
|
|
30
|
+
import { getRole, loadAllRoles } from "./workspace/roles.js";
|
|
31
|
+
import { WORKSPACE_PATHS } from "./workspace/paths.js";
|
|
32
|
+
import { serverError } from "./utils/httpError.js";
|
|
33
|
+
import { mcpToolsRouter, mcpTools, isMcpToolEnabled } from "./agent/mcp-tools/index.js";
|
|
34
|
+
import { initWorkspace, workspacePath } from "./workspace/workspace.js";
|
|
35
|
+
import { env, isGeminiAvailable } from "./system/env.js";
|
|
36
|
+
import { buildSandboxStatus } from "./api/sandboxStatus.js";
|
|
37
|
+
import fs from "fs";
|
|
38
|
+
import os from "os";
|
|
39
|
+
import { isDockerAvailable, ensureSandboxImage } from "./system/docker.js";
|
|
40
|
+
import { maybeRunJournal } from "./workspace/journal/index.js";
|
|
41
|
+
import { backfillAllSessions } from "./workspace/chat-index/index.js";
|
|
42
|
+
import { createPubSub } from "./events/pub-sub/index.js";
|
|
43
|
+
import { PUBSUB_CHANNELS } from "../src/config/pubsubChannels.js";
|
|
44
|
+
import { createTaskManager } from "./events/task-manager/index.js";
|
|
45
|
+
import type { ITaskManager } from "./events/task-manager/index.js";
|
|
46
|
+
import { initScheduler, type SystemTaskDef } from "./events/scheduler-adapter.js";
|
|
47
|
+
import schedulerTasksRoutes from "./api/routes/schedulerTasks.js";
|
|
48
|
+
import { loadSchedulerOverrides, UTC_HH_MM_RE } from "./utils/files/scheduler-overrides-io.js";
|
|
49
|
+
import type { IPubSub } from "./events/pub-sub/index.js";
|
|
50
|
+
import { initSessionStore } from "./events/session-store/index.js";
|
|
51
|
+
import { connectRelay } from "./events/relay-client.js";
|
|
52
|
+
import { requireSameOrigin } from "./api/csrfGuard.js";
|
|
53
|
+
import { bearerAuth } from "./api/auth/bearerAuth.js";
|
|
54
|
+
import { deleteTokenFile, generateAndWriteToken, getCurrentToken } from "./api/auth/token.js";
|
|
55
|
+
import { log } from "./system/logger/index.js";
|
|
56
|
+
import { startChat } from "./api/routes/agent.js";
|
|
57
|
+
import { registerScheduledSkills } from "./workspace/skills/scheduler.js";
|
|
58
|
+
import { registerUserTasks } from "./workspace/skills/user-tasks.js";
|
|
59
|
+
import { API_ROUTES } from "../src/config/apiRoutes.js";
|
|
60
|
+
import { EVENT_TYPES } from "../src/types/events.js";
|
|
61
|
+
import { SESSION_ORIGINS } from "../src/types/session.js";
|
|
62
|
+
import { ONE_SECOND_MS, ONE_MINUTE_MS, ONE_HOUR_MS } from "./utils/time.js";
|
|
63
|
+
import { SCHEDULE_TYPES, MISSED_RUN_POLICIES } from "@receptron/task-scheduler";
|
|
64
|
+
|
|
65
|
+
const HTML_TOKEN_PLACEHOLDER = "__MULMOCLAUDE_AUTH_TOKEN__";
|
|
66
|
+
|
|
67
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
68
|
+
const __dirname = path.dirname(__filename);
|
|
69
|
+
|
|
70
|
+
const debugMode = process.argv.includes("--debug");
|
|
71
|
+
|
|
72
|
+
initWorkspace();
|
|
73
|
+
|
|
74
|
+
let sandboxEnabled = false;
|
|
75
|
+
|
|
76
|
+
const app = express();
|
|
77
|
+
const PORT = env.port;
|
|
78
|
+
|
|
79
|
+
app.disable("x-powered-by");
|
|
80
|
+
// No `cors()` middleware. The Vite dev proxy forwards `/api/*`
|
|
81
|
+
// from :5173 to :3001 server-side, and in production Express
|
|
82
|
+
// serves the built client from the same origin, so every
|
|
83
|
+
// legitimate request is same-origin and doesn't need CORS
|
|
84
|
+
// headers at all. Dropping the middleware means a page at
|
|
85
|
+
// `http://evil.example` can still send a request to
|
|
86
|
+
// `localhost:3001` but the browser refuses to expose the
|
|
87
|
+
// response to the calling script (no
|
|
88
|
+
// `Access-Control-Allow-Origin` header). See
|
|
89
|
+
// plans/done/fix-server-lockdown-cors-localhost.md for the threat
|
|
90
|
+
// model.
|
|
91
|
+
app.use(express.json({ limit: "50mb" }));
|
|
92
|
+
// CSRF guard: reject state-changing requests that arrive with a
|
|
93
|
+
// non-localhost Origin header. Allows missing Origin (server-to-
|
|
94
|
+
// server / CLI callers) because the listener is already bound to
|
|
95
|
+
// localhost (#148); if that ever changes, tighten this middleware
|
|
96
|
+
// too. See plans/done/fix-server-csrf-origin-check.md.
|
|
97
|
+
app.use(requireSameOrigin);
|
|
98
|
+
|
|
99
|
+
// Bearer token auth: every `/api/*` request must carry
|
|
100
|
+
// `Authorization: Bearer <token>` matching the per-startup token.
|
|
101
|
+
// Layered *on top of* CSRF guard so we catch both cross-origin
|
|
102
|
+
// browser attacks (origin check) and local sibling processes that
|
|
103
|
+
// bypass browser CORS (bearer check). See #272 and
|
|
104
|
+
// plans/feat-bearer-token-auth.md.
|
|
105
|
+
//
|
|
106
|
+
// /api/files/* is exempt because <img src="/api/files/raw?path=...">
|
|
107
|
+
// tags in rendered markdown can't attach Authorization headers.
|
|
108
|
+
// The CSRF origin check + loopback-only binding still apply.
|
|
109
|
+
app.use("/api", (req, res, next) => {
|
|
110
|
+
if (req.path.startsWith("/files/")) return next();
|
|
111
|
+
bearerAuth(req, res, next);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
app.get(API_ROUTES.health, (_req: Request, res: Response) => {
|
|
115
|
+
res.json({
|
|
116
|
+
status: "OK",
|
|
117
|
+
geminiAvailable: isGeminiAvailable(),
|
|
118
|
+
sandboxEnabled,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Sandbox credential-forwarding state (#329). Returns `{}` when the
|
|
123
|
+
// sandbox is disabled — the popup already renders a distinct
|
|
124
|
+
// "No sandbox" branch in that case and extra fields would be noise.
|
|
125
|
+
// When enabled, returns `{ sshAgent, mounts }`; full debug detail
|
|
126
|
+
// (host paths, skip reasons, unknown names) stays in the server log.
|
|
127
|
+
app.get(API_ROUTES.sandbox, (_req: Request, res: Response) => {
|
|
128
|
+
const status = buildSandboxStatus({
|
|
129
|
+
sandboxEnabled,
|
|
130
|
+
sshAgentForward: env.sandboxSshAgentForward,
|
|
131
|
+
configMountNames: env.sandboxMountConfigs,
|
|
132
|
+
sshAuthSock: process.env.SSH_AUTH_SOCK,
|
|
133
|
+
});
|
|
134
|
+
res.json(status ?? {});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Routers register FULL `/api/...` paths internally (see
|
|
138
|
+
// `src/config/apiRoutes.ts`), so they mount at root. The previous
|
|
139
|
+
// `app.use("/api", ...)` prefix was dropped when #289 part 1 moved
|
|
140
|
+
// the `/api` literal into each `router.post(API_ROUTES.…)` call.
|
|
141
|
+
app.use(agentRoutes);
|
|
142
|
+
app.use(todosRoutes);
|
|
143
|
+
app.use(schedulerRoutes);
|
|
144
|
+
app.use(sessionsRoutes);
|
|
145
|
+
app.use(chatIndexRoutes);
|
|
146
|
+
app.use(sourcesRoutes);
|
|
147
|
+
app.use(pluginsRoutes);
|
|
148
|
+
app.use(imageRoutes);
|
|
149
|
+
app.use(presentHtmlRoutes);
|
|
150
|
+
app.use(chartRoutes);
|
|
151
|
+
app.use(rolesRoutes);
|
|
152
|
+
app.use(mulmoScriptRoutes);
|
|
153
|
+
app.use(wikiRoutes);
|
|
154
|
+
app.use(pdfRoutes);
|
|
155
|
+
app.use(filesRoutes);
|
|
156
|
+
app.use(configRoutes);
|
|
157
|
+
app.use(skillsRoutes);
|
|
158
|
+
async function listSessionsForBridge(opts: { limit: number; offset: number }) {
|
|
159
|
+
const rows = await loadAllSessions();
|
|
160
|
+
const sorted = rows.sort((a, b) => b.changeMs - a.changeMs);
|
|
161
|
+
const total = sorted.length;
|
|
162
|
+
const sessions = sorted.slice(opts.offset, opts.offset + opts.limit).map((r) => ({
|
|
163
|
+
id: r.summary.id,
|
|
164
|
+
roleId: r.summary.roleId,
|
|
165
|
+
preview: r.summary.preview,
|
|
166
|
+
updatedAt: r.summary.updatedAt,
|
|
167
|
+
}));
|
|
168
|
+
return { sessions, total };
|
|
169
|
+
}
|
|
170
|
+
async function getSessionHistoryForBridge(sessionId: string, opts: { limit: number; offset: number }) {
|
|
171
|
+
const content = await readSessionJsonl(sessionId);
|
|
172
|
+
if (!content) return { messages: [], total: 0 };
|
|
173
|
+
const allMessages: Array<{ source: string; text: string }> = [];
|
|
174
|
+
const lines = content.split("\n").filter(Boolean);
|
|
175
|
+
// Collect all text events newest-first
|
|
176
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
177
|
+
try {
|
|
178
|
+
const entry = JSON.parse(lines[i]);
|
|
179
|
+
if (entry.type === EVENT_TYPES.text && typeof entry.message === "string") {
|
|
180
|
+
allMessages.push({
|
|
181
|
+
source: entry.source ?? "unknown",
|
|
182
|
+
text: entry.message,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
// skip malformed lines
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const total = allMessages.length;
|
|
190
|
+
const messages = allMessages.slice(opts.offset, opts.offset + opts.limit);
|
|
191
|
+
return { messages, total };
|
|
192
|
+
}
|
|
193
|
+
const chatService = createChatService({
|
|
194
|
+
startChat,
|
|
195
|
+
onSessionEvent,
|
|
196
|
+
loadAllRoles,
|
|
197
|
+
getRole,
|
|
198
|
+
defaultRoleId: DEFAULT_ROLE_ID,
|
|
199
|
+
transportsDir: WORKSPACE_PATHS.transports,
|
|
200
|
+
logger: log,
|
|
201
|
+
// Socket.io handshake (see #268 Phase A) needs to validate the
|
|
202
|
+
// same bearer token the HTTP middleware enforces.
|
|
203
|
+
tokenProvider: getCurrentToken,
|
|
204
|
+
listSessions: listSessionsForBridge,
|
|
205
|
+
getSessionHistory: getSessionHistoryForBridge,
|
|
206
|
+
});
|
|
207
|
+
app.use(chatService.router);
|
|
208
|
+
|
|
209
|
+
// Notifications router. The route file needs the pub-sub publisher
|
|
210
|
+
// (only created inside `startRuntimeServices` after `app.listen`) and
|
|
211
|
+
// the chat-service push handle (available at module scope). We mount
|
|
212
|
+
// the router now so it sits behind the same bearer middleware as
|
|
213
|
+
// every other /api route, and back-fill the pub-sub dep once
|
|
214
|
+
// `startRuntimeServices` has it. Calls that arrive before fill-in
|
|
215
|
+
// (impossible in practice — the HTTP server isn't listening yet)
|
|
216
|
+
// would no-op on publish but still queue the bridge push.
|
|
217
|
+
const notificationDeps: NotificationDeps = {
|
|
218
|
+
publish: () => {
|
|
219
|
+
/* replaced by startRuntimeServices */
|
|
220
|
+
},
|
|
221
|
+
pushToBridge: chatService.pushToBridge,
|
|
222
|
+
};
|
|
223
|
+
app.use(createNotificationsRouter(notificationDeps));
|
|
224
|
+
app.use(mcpToolsRouter);
|
|
225
|
+
app.use(schedulerTasksRoutes);
|
|
226
|
+
|
|
227
|
+
if (env.isProduction) {
|
|
228
|
+
// `{ index: false }` so express.static doesn't intercept `GET /`
|
|
229
|
+
// with the built index.html. We need our own handler that reads
|
|
230
|
+
// the file and substitutes the bearer token placeholder on each
|
|
231
|
+
// request — see the wildcard fallback below.
|
|
232
|
+
app.use(express.static(path.join(__dirname, "../client"), { index: false }));
|
|
233
|
+
const indexHtmlPath = path.join(__dirname, "../client/index.html");
|
|
234
|
+
app.get("/{*splat}", (_req: Request, res: Response) => {
|
|
235
|
+
let html: string;
|
|
236
|
+
try {
|
|
237
|
+
html = fs.readFileSync(indexHtmlPath, "utf-8");
|
|
238
|
+
} catch (err) {
|
|
239
|
+
log.error("server", "failed to read index.html", { error: String(err) });
|
|
240
|
+
serverError(res, "Internal Server Error");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const token = getCurrentToken() ?? "";
|
|
244
|
+
html = html.replace(HTML_TOKEN_PLACEHOLDER, token);
|
|
245
|
+
res.set("Content-Type", "text/html; charset=utf-8");
|
|
246
|
+
res.send(html);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
app.use((err: Error, _req: Request, res: Response, __next: NextFunction) => {
|
|
251
|
+
log.error("express", "unhandled error", {
|
|
252
|
+
error: err.message,
|
|
253
|
+
stack: err.stack,
|
|
254
|
+
});
|
|
255
|
+
serverError(res, "Internal Server Error");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
function isPortFree(port: number): Promise<boolean> {
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
const server = net.createServer();
|
|
261
|
+
server.once("error", () => resolve(false));
|
|
262
|
+
server.once("listening", () => {
|
|
263
|
+
server.close(() => resolve(true));
|
|
264
|
+
});
|
|
265
|
+
// Probe the same interface we'll actually bind to so a port
|
|
266
|
+
// held by a different process on a different interface doesn't
|
|
267
|
+
// give us a false "free" reading.
|
|
268
|
+
server.listen(port, "127.0.0.1");
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function ensureCredentialsAvailable(): Promise<void> {
|
|
273
|
+
const credentialsPath = path.join(os.homedir(), ".claude", ".credentials.json");
|
|
274
|
+
if (fs.existsSync(credentialsPath)) return;
|
|
275
|
+
|
|
276
|
+
if (process.platform === "darwin") {
|
|
277
|
+
const { refreshCredentials } = await import("./system/credentials.js");
|
|
278
|
+
const ok = await refreshCredentials();
|
|
279
|
+
if (ok) return;
|
|
280
|
+
log.error("sandbox", "Failed to export credentials from macOS Keychain. Run `npm run sandbox:login` manually.");
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
log.error("sandbox", "Missing credentials file at ~/.claude/.credentials.json. Run `claude auth login` to authenticate Claude Code.");
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function setupSandbox(): Promise<boolean> {
|
|
288
|
+
if (env.disableSandbox) {
|
|
289
|
+
log.info("sandbox", "DISABLE_SANDBOX=1 — running unrestricted (debug mode)");
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const dockerAvailable = await isDockerAvailable();
|
|
294
|
+
if (!dockerAvailable) {
|
|
295
|
+
log.info("sandbox", "Docker not found — claude will run unrestricted");
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
await ensureCredentialsAvailable();
|
|
299
|
+
log.info("sandbox", "Docker available — building sandbox image if needed");
|
|
300
|
+
await ensureSandboxImage();
|
|
301
|
+
log.info("sandbox", "Sandbox ready");
|
|
302
|
+
return true;
|
|
303
|
+
} catch (err) {
|
|
304
|
+
log.error("sandbox", "Failed to set up sandbox, running unrestricted", {
|
|
305
|
+
error: String(err),
|
|
306
|
+
});
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function logMcpStatus(): void {
|
|
312
|
+
const enabledMcpTools = mcpTools.filter(isMcpToolEnabled);
|
|
313
|
+
const disabledMcpTools = mcpTools.filter((t) => !isMcpToolEnabled(t));
|
|
314
|
+
if (enabledMcpTools.length > 0) {
|
|
315
|
+
log.info("mcp", "Available", {
|
|
316
|
+
tools: enabledMcpTools.map((t) => t.definition.name).join(", "),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (disabledMcpTools.length > 0) {
|
|
320
|
+
const names = disabledMcpTools.map((t) => t.definition.name + " (" + (t.requiredEnv ?? []).join(", ") + ")").join(", ");
|
|
321
|
+
log.info("mcp", "Unavailable (missing env)", { tools: names });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function maybeForceJournalRun(): void {
|
|
326
|
+
// Debug switch: set JOURNAL_FORCE_RUN_ON_STARTUP=1 to run a full
|
|
327
|
+
// journal pass immediately without waiting for a session end or
|
|
328
|
+
// the hourly interval. Fire-and-forget — journal errors never
|
|
329
|
+
// propagate out of maybeRunJournal.
|
|
330
|
+
if (!env.journalForceRunOnStartup) return;
|
|
331
|
+
log.info("journal", "JOURNAL_FORCE_RUN_ON_STARTUP=1 — running now");
|
|
332
|
+
maybeRunJournal({ force: true }).catch((err) => {
|
|
333
|
+
log.warn("journal", "forced startup run failed", { error: String(err) });
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function maybeForceChatIndexBackfill(): void {
|
|
338
|
+
// Companion switch for the chat indexer: force-rebuild every
|
|
339
|
+
// session's title summary on startup. Useful the first time the
|
|
340
|
+
// feature is rolled out over an existing workspace, or when
|
|
341
|
+
// debugging the indexer itself.
|
|
342
|
+
if (!env.chatIndexForceRunOnStartup) return;
|
|
343
|
+
log.info("chat-index", "CHAT_INDEX_FORCE_RUN_ON_STARTUP=1 — running now");
|
|
344
|
+
backfillAllSessions()
|
|
345
|
+
.then((result) => {
|
|
346
|
+
log.info("chat-index", "startup backfill complete", {
|
|
347
|
+
indexed: result.indexed,
|
|
348
|
+
total: result.total,
|
|
349
|
+
skipped: result.skipped,
|
|
350
|
+
});
|
|
351
|
+
})
|
|
352
|
+
.catch((err) => {
|
|
353
|
+
log.warn("chat-index", "forced startup backfill failed", {
|
|
354
|
+
error: String(err),
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function startRuntimeServices(httpServer: ReturnType<typeof app.listen>): void {
|
|
360
|
+
log.info("server", "listening", { port: PORT });
|
|
361
|
+
|
|
362
|
+
// --- Pub/Sub ---
|
|
363
|
+
const pubsub = createPubSub(httpServer);
|
|
364
|
+
// Back-fill the notifications router with the live publisher (see
|
|
365
|
+
// module-scope placeholder above).
|
|
366
|
+
notificationDeps.publish = (channel, payload) => pubsub.publish(channel, payload);
|
|
367
|
+
|
|
368
|
+
// --- Notification system (#144) ---
|
|
369
|
+
initNotifications({
|
|
370
|
+
publish: (channel, payload) => pubsub.publish(channel, payload),
|
|
371
|
+
pushToBridge: chatService.pushToBridge,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// --- Chat socket transport (Phase A of #268) ---
|
|
375
|
+
chatService.attachSocket(httpServer);
|
|
376
|
+
|
|
377
|
+
// --- Relay WebSocket client ---
|
|
378
|
+
if (env.relayUrl && env.relayToken) {
|
|
379
|
+
connectRelay({
|
|
380
|
+
relayUrl: env.relayUrl,
|
|
381
|
+
relayToken: env.relayToken,
|
|
382
|
+
relay: chatService.relay,
|
|
383
|
+
logger: log,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// --- Session Store ---
|
|
388
|
+
initSessionStore(pubsub);
|
|
389
|
+
|
|
390
|
+
// --- Task Manager ---
|
|
391
|
+
const taskManager = createTaskManager({
|
|
392
|
+
tickMs: debugMode ? ONE_SECOND_MS : ONE_MINUTE_MS,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (debugMode) {
|
|
396
|
+
registerDebugTasks(taskManager, pubsub);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// --- Scheduler (Phase 1 of #357) ---
|
|
400
|
+
// Register system tasks with persistence + catch-up. The journal
|
|
401
|
+
// and chat-index also fire from the agent finally-hook for
|
|
402
|
+
// responsiveness; the scheduler ensures catch-up after gaps.
|
|
403
|
+
const systemTasks: SystemTaskDef[] = [
|
|
404
|
+
{
|
|
405
|
+
id: "system:journal",
|
|
406
|
+
name: "Journal daily pass",
|
|
407
|
+
description: "Summarize recent chat sessions into daily + topic files",
|
|
408
|
+
schedule: { type: SCHEDULE_TYPES.interval, intervalMs: ONE_HOUR_MS },
|
|
409
|
+
missedRunPolicy: MISSED_RUN_POLICIES.runOnce,
|
|
410
|
+
run: () => maybeRunJournal({}),
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: "system:chat-index",
|
|
414
|
+
name: "Chat index backfill",
|
|
415
|
+
description: "Generate AI titles + summaries for un-indexed sessions",
|
|
416
|
+
schedule: { type: SCHEDULE_TYPES.interval, intervalMs: ONE_HOUR_MS },
|
|
417
|
+
missedRunPolicy: MISSED_RUN_POLICIES.runOnce,
|
|
418
|
+
run: () => backfillAllSessions().then(() => {}),
|
|
419
|
+
},
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
// Apply user-configurable schedule overrides from
|
|
423
|
+
// config/scheduler/overrides.json. Missing file or unknown keys
|
|
424
|
+
// are silently ignored — the hardcoded defaults above remain.
|
|
425
|
+
const overrides = loadSchedulerOverrides();
|
|
426
|
+
for (const task of systemTasks) {
|
|
427
|
+
const ovr = overrides[task.id];
|
|
428
|
+
if (!ovr) continue;
|
|
429
|
+
if (task.schedule.type === SCHEDULE_TYPES.interval && typeof ovr.intervalMs === "number" && ovr.intervalMs > 0) {
|
|
430
|
+
log.info("scheduler", "applying override", {
|
|
431
|
+
id: task.id,
|
|
432
|
+
intervalMs: ovr.intervalMs,
|
|
433
|
+
});
|
|
434
|
+
task.schedule = {
|
|
435
|
+
type: SCHEDULE_TYPES.interval,
|
|
436
|
+
intervalMs: ovr.intervalMs,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (task.schedule.type === SCHEDULE_TYPES.daily && typeof ovr.time === "string" && UTC_HH_MM_RE.test(ovr.time)) {
|
|
440
|
+
log.info("scheduler", "applying override", {
|
|
441
|
+
id: task.id,
|
|
442
|
+
time: ovr.time,
|
|
443
|
+
});
|
|
444
|
+
task.schedule = { type: SCHEDULE_TYPES.daily, time: ovr.time };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
initScheduler(taskManager, systemTasks).catch((err) => {
|
|
449
|
+
log.error("scheduler", "init failed (non-fatal)", {
|
|
450
|
+
error: String(err),
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Register skills with schedule: frontmatter as scheduled tasks.
|
|
455
|
+
// Fire-and-forget — skill scan errors are logged but don't block
|
|
456
|
+
// server startup.
|
|
457
|
+
registerScheduledSkills({
|
|
458
|
+
taskManager,
|
|
459
|
+
workspaceRoot: workspacePath,
|
|
460
|
+
startChat,
|
|
461
|
+
})
|
|
462
|
+
.then((count) => {
|
|
463
|
+
if (count > 0) {
|
|
464
|
+
log.info("skills", "scheduled skills registered", { count });
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
.catch((err) => {
|
|
468
|
+
log.warn("skills", "failed to register scheduled skills", {
|
|
469
|
+
error: String(err),
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Register user-created scheduled tasks from tasks.json.
|
|
474
|
+
registerUserTasks({ taskManager, startChat })
|
|
475
|
+
.then((count) => {
|
|
476
|
+
if (count > 0) {
|
|
477
|
+
log.info("user-tasks", "user tasks registered", { count });
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
.catch((err) => {
|
|
481
|
+
log.warn("user-tasks", "failed to register user tasks", {
|
|
482
|
+
error: String(err),
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
taskManager.start();
|
|
487
|
+
|
|
488
|
+
maybeForceJournalRun();
|
|
489
|
+
maybeForceChatIndexBackfill();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Graceful shutdown: best-effort cleanup of the auth token file so
|
|
493
|
+
// other readers (Vite plugin, future bridges) don't latch onto a
|
|
494
|
+
// dead token. Crashes that skip this are harmless — see
|
|
495
|
+
// plans/feat-bearer-token-auth.md; the next startup overwrites and
|
|
496
|
+
// the stale file's token no longer matches the live in-memory one.
|
|
497
|
+
let isShuttingDown = false;
|
|
498
|
+
async function gracefulShutdown(signal: string): Promise<void> {
|
|
499
|
+
if (isShuttingDown) return;
|
|
500
|
+
isShuttingDown = true;
|
|
501
|
+
log.info("server", "shutting down", { signal });
|
|
502
|
+
await deleteTokenFile();
|
|
503
|
+
process.exit(0);
|
|
504
|
+
}
|
|
505
|
+
process.on("SIGINT", () => {
|
|
506
|
+
gracefulShutdown("SIGINT").catch(() => process.exit(1));
|
|
507
|
+
});
|
|
508
|
+
process.on("SIGTERM", () => {
|
|
509
|
+
gracefulShutdown("SIGTERM").catch(() => process.exit(1));
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
(async () => {
|
|
513
|
+
const portFree = await isPortFree(PORT);
|
|
514
|
+
if (!portFree) {
|
|
515
|
+
log.error("server", `Port ${PORT} is already in use. Stop the other process and try again.`);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Generate the bearer token before `app.listen` so the first
|
|
520
|
+
// request cannot race an uninitialised `getCurrentToken()`. The
|
|
521
|
+
// middleware defensively handles the null case anyway (401).
|
|
522
|
+
// `env.authTokenOverride` (#316) pins the token across restarts
|
|
523
|
+
// when set; otherwise a fresh random one is written.
|
|
524
|
+
await generateAndWriteToken(undefined, env.authTokenOverride);
|
|
525
|
+
log.info("auth", "bearer token written", {
|
|
526
|
+
path: WORKSPACE_PATHS.sessionToken,
|
|
527
|
+
source: env.authTokenOverride ? "env" : "random",
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
sandboxEnabled = await setupSandbox();
|
|
531
|
+
logMcpStatus();
|
|
532
|
+
|
|
533
|
+
// Bind to localhost-only. Using `0.0.0.0` would expose the dev
|
|
534
|
+
// server to the entire LAN (anyone on the same Wi-Fi could reach
|
|
535
|
+
// `http://<laptop-ip>:3001/api/*`), which combined with the
|
|
536
|
+
// workspace file API is a credential-theft risk. Personal dev
|
|
537
|
+
// tool — localhost is the right default.
|
|
538
|
+
const httpServer = app.listen(PORT, "127.0.0.1", () => {
|
|
539
|
+
startRuntimeServices(httpServer);
|
|
540
|
+
});
|
|
541
|
+
})();
|
|
542
|
+
|
|
543
|
+
function registerDebugTasks(taskManager: ITaskManager, pubsub: IPubSub) {
|
|
544
|
+
let tick = 0;
|
|
545
|
+
|
|
546
|
+
taskManager.registerTask({
|
|
547
|
+
id: "debug.auto-chat",
|
|
548
|
+
description: "Debug — toggles title color 10 times then starts a General-mode chat, then self-removes",
|
|
549
|
+
schedule: { type: SCHEDULE_TYPES.interval, intervalMs: ONE_SECOND_MS },
|
|
550
|
+
run: async () => {
|
|
551
|
+
tick++;
|
|
552
|
+
const last = tick === 10;
|
|
553
|
+
log.info("debug", `auto-chat countdown ${tick}/10`);
|
|
554
|
+
pubsub.publish(PUBSUB_CHANNELS.debugBeat, { count: tick, last });
|
|
555
|
+
|
|
556
|
+
if (!last) return;
|
|
557
|
+
|
|
558
|
+
taskManager.removeTask("debug.auto-chat");
|
|
559
|
+
const chatSessionId = crypto.randomUUID();
|
|
560
|
+
log.info("debug", "starting auto-chat", { chatSessionId });
|
|
561
|
+
const result = await startChat({
|
|
562
|
+
message: "Tell me about this app, MulmoClaude.",
|
|
563
|
+
roleId: DEFAULT_ROLE_ID,
|
|
564
|
+
chatSessionId,
|
|
565
|
+
origin: SESSION_ORIGINS.scheduler,
|
|
566
|
+
});
|
|
567
|
+
log.info("debug", "auto-chat result", { kind: result.kind });
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
log.info("debug", "Debug mode active — registered debug tasks");
|
|
572
|
+
}
|