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
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full bg-white flex flex-col">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-100 shrink-0">
|
|
5
|
+
<div class="flex items-center gap-3">
|
|
6
|
+
<button v-if="action !== 'index'" class="text-gray-400 hover:text-gray-700" title="Back to index" @click="navigate('index')">
|
|
7
|
+
<span class="material-icons text-base">arrow_back</span>
|
|
8
|
+
</button>
|
|
9
|
+
<h2 class="text-lg font-semibold text-gray-800">{{ title }}</h2>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="flex gap-1 items-center">
|
|
12
|
+
<template v-if="action === 'page' && content">
|
|
13
|
+
<button
|
|
14
|
+
class="px-3 py-1 text-xs rounded-full border transition-colors border-gray-200 text-gray-500 hover:bg-gray-50 disabled:opacity-40 w-16 flex items-center justify-center gap-1"
|
|
15
|
+
:disabled="pdfDownloading"
|
|
16
|
+
@click="downloadPdf"
|
|
17
|
+
>
|
|
18
|
+
<svg v-if="pdfDownloading" class="animate-spin w-3 h-3 shrink-0" viewBox="0 0 24 24" fill="none">
|
|
19
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
20
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
|
21
|
+
</svg>
|
|
22
|
+
<span v-else>↓ PDF</span>
|
|
23
|
+
<span v-if="pdfDownloading">PDF</span>
|
|
24
|
+
</button>
|
|
25
|
+
<span v-if="pdfError" class="text-xs text-red-500" :title="pdfError">⚠ PDF failed</span>
|
|
26
|
+
</template>
|
|
27
|
+
<button
|
|
28
|
+
class="px-3 py-1 text-xs rounded-full border transition-colors"
|
|
29
|
+
:class="action === 'index' ? 'border-blue-400 bg-blue-50 text-blue-700' : 'border-gray-200 text-gray-500 hover:bg-gray-50'"
|
|
30
|
+
@click="navigate('index')"
|
|
31
|
+
>
|
|
32
|
+
Index
|
|
33
|
+
</button>
|
|
34
|
+
<button
|
|
35
|
+
class="px-3 py-1 text-xs rounded-full border transition-colors"
|
|
36
|
+
:class="action === 'log' ? 'border-blue-400 bg-blue-50 text-blue-700' : 'border-gray-200 text-gray-500 hover:bg-gray-50'"
|
|
37
|
+
@click="navigate('log')"
|
|
38
|
+
>
|
|
39
|
+
Log
|
|
40
|
+
</button>
|
|
41
|
+
<button
|
|
42
|
+
class="px-3 py-1 text-xs rounded-full border transition-colors"
|
|
43
|
+
:class="action === 'lint_report' ? 'border-blue-400 bg-blue-50 text-blue-700' : 'border-gray-200 text-gray-500 hover:bg-gray-50'"
|
|
44
|
+
@click="navigate('lint_report')"
|
|
45
|
+
>
|
|
46
|
+
Lint
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Navigation error -->
|
|
52
|
+
<div v-if="navError" class="mx-6 mt-4 rounded border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">
|
|
53
|
+
{{ navError }}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Empty state -->
|
|
57
|
+
<div v-if="!content && !navError" class="flex-1 flex items-center justify-center text-gray-400 text-sm">
|
|
58
|
+
<div class="text-center space-y-2">
|
|
59
|
+
<span class="material-icons text-4xl text-gray-300">menu_book</span>
|
|
60
|
+
<p>Wiki is empty. Ask the Wiki Manager to ingest a source.</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Index: page card list -->
|
|
65
|
+
<div v-else-if="action === 'index' && pageEntries && pageEntries.length > 0" class="flex-1 overflow-y-auto p-4 space-y-2">
|
|
66
|
+
<div
|
|
67
|
+
v-for="entry in pageEntries"
|
|
68
|
+
:key="entry.slug"
|
|
69
|
+
class="rounded-lg border border-gray-200 p-3 cursor-pointer hover:border-blue-300 hover:bg-blue-50 transition-colors"
|
|
70
|
+
@click="navigatePage(entry.slug || entry.title)"
|
|
71
|
+
>
|
|
72
|
+
<div class="font-medium text-sm text-gray-800">{{ entry.title }}</div>
|
|
73
|
+
<div v-if="entry.description" class="text-xs text-gray-500 mt-0.5">
|
|
74
|
+
{{ entry.description }}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Markdown content -->
|
|
80
|
+
<div v-else class="flex-1 overflow-y-auto px-6 py-4 prose prose-sm max-w-none wiki-content" @click="handleContentClick" v-html="renderedContent" />
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script setup lang="ts">
|
|
85
|
+
import { computed, ref, watch } from "vue";
|
|
86
|
+
import { marked } from "marked";
|
|
87
|
+
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
88
|
+
import type { WikiData, WikiPageEntry } from "./index";
|
|
89
|
+
import { handleExternalLinkClick } from "../../utils/dom/externalLink";
|
|
90
|
+
import { useFreshPluginData } from "../../composables/useFreshPluginData";
|
|
91
|
+
import { renderWikiLinks } from "./helpers";
|
|
92
|
+
import { rewriteMarkdownImageRefs } from "../../utils/image/rewriteMarkdownImageRefs";
|
|
93
|
+
import { apiPost, apiFetchRaw } from "../../utils/api";
|
|
94
|
+
import { API_ROUTES } from "../../config/apiRoutes";
|
|
95
|
+
import { errorMessage } from "../../utils/errors";
|
|
96
|
+
|
|
97
|
+
const props = defineProps<{
|
|
98
|
+
selectedResult?: ToolResultComplete<WikiData>;
|
|
99
|
+
sendTextMessage?: (text: string) => void;
|
|
100
|
+
}>();
|
|
101
|
+
const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
|
|
102
|
+
|
|
103
|
+
const action = ref(props.selectedResult?.data?.action ?? "index");
|
|
104
|
+
const title = ref(props.selectedResult?.data?.title ?? "Wiki");
|
|
105
|
+
const content = ref(props.selectedResult?.data?.content ?? "");
|
|
106
|
+
const pageEntries = ref<WikiPageEntry[]>(props.selectedResult?.data?.pageEntries ?? []);
|
|
107
|
+
|
|
108
|
+
const { refresh } = useFreshPluginData<WikiData>({
|
|
109
|
+
// Slug-aware: when the view is currently showing a specific page,
|
|
110
|
+
// fetch that page by slug; otherwise fetch the index.
|
|
111
|
+
endpoint: () => {
|
|
112
|
+
const slug = action.value === "page" ? props.selectedResult?.data?.pageName : undefined;
|
|
113
|
+
return slug ? `${API_ROUTES.wiki.base}?slug=${encodeURIComponent(slug)}` : API_ROUTES.wiki.base;
|
|
114
|
+
},
|
|
115
|
+
extract: (json) => (json as { data?: WikiData }).data ?? null,
|
|
116
|
+
apply: (data) => {
|
|
117
|
+
action.value = data.action ?? "index";
|
|
118
|
+
title.value = data.title ?? "Wiki";
|
|
119
|
+
content.value = data.content ?? "";
|
|
120
|
+
pageEntries.value = data.pageEntries ?? [];
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
watch(
|
|
125
|
+
() => props.selectedResult?.uuid,
|
|
126
|
+
() => {
|
|
127
|
+
const data = props.selectedResult?.data;
|
|
128
|
+
if (data) {
|
|
129
|
+
action.value = data.action ?? "index";
|
|
130
|
+
title.value = data.title ?? "Wiki";
|
|
131
|
+
content.value = data.content ?? "";
|
|
132
|
+
pageEntries.value = data.pageEntries ?? [];
|
|
133
|
+
}
|
|
134
|
+
void refresh();
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const renderedContent = computed(() => {
|
|
139
|
+
if (!content.value) return "";
|
|
140
|
+
// Rewrite workspace-relative image refs (``)
|
|
141
|
+
// to `/api/files/raw?path=...` BEFORE marked parses them — without
|
|
142
|
+
// this, the browser tries to fetch against the SPA route URL
|
|
143
|
+
// (/chat/…/images/foo.png) and 404s. basePath = wiki/pages for
|
|
144
|
+
// individual pages so `../images/foo.png` resolves correctly.
|
|
145
|
+
const basePath = action.value === "page" ? "wiki/pages" : "wiki";
|
|
146
|
+
const withImages = rewriteMarkdownImageRefs(content.value, basePath);
|
|
147
|
+
return marked.parse(renderWikiLinks(withImages)) as string;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const navError = ref<string | null>(null);
|
|
151
|
+
const pdfDownloading = ref(false);
|
|
152
|
+
const pdfError = ref<string | null>(null);
|
|
153
|
+
|
|
154
|
+
async function callApi(body: Record<string, unknown>) {
|
|
155
|
+
navError.value = null;
|
|
156
|
+
const response = await apiPost<{
|
|
157
|
+
data?: {
|
|
158
|
+
action?: string;
|
|
159
|
+
title?: string;
|
|
160
|
+
content?: string;
|
|
161
|
+
pageEntries?: WikiPageEntry[];
|
|
162
|
+
};
|
|
163
|
+
}>(API_ROUTES.wiki.base, body);
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
navError.value = response.status === 0 ? response.error : `Wiki API error ${response.status}: ${response.error}`;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const result = response.data;
|
|
169
|
+
action.value = result.data?.action ?? "index";
|
|
170
|
+
title.value = result.data?.title ?? "Wiki";
|
|
171
|
+
content.value = result.data?.content ?? "";
|
|
172
|
+
pageEntries.value = result.data?.pageEntries ?? [];
|
|
173
|
+
if (props.selectedResult) {
|
|
174
|
+
emit("updateResult", {
|
|
175
|
+
...props.selectedResult,
|
|
176
|
+
...result,
|
|
177
|
+
toolName: "manageWiki",
|
|
178
|
+
uuid: props.selectedResult.uuid,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function navigate(newAction: string) {
|
|
184
|
+
callApi({ action: newAction });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function navigatePage(pageName: string) {
|
|
188
|
+
callApi({ action: "page", pageName });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function downloadPdf() {
|
|
192
|
+
pdfError.value = null;
|
|
193
|
+
pdfDownloading.value = true;
|
|
194
|
+
let response: Response;
|
|
195
|
+
try {
|
|
196
|
+
response = await apiFetchRaw(API_ROUTES.pdf.markdown, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
markdown: content.value,
|
|
200
|
+
filename: `${title.value}.pdf`,
|
|
201
|
+
}),
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
});
|
|
204
|
+
} catch (err) {
|
|
205
|
+
pdfError.value = errorMessage(err);
|
|
206
|
+
pdfDownloading.value = false;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
const text = await response.text().catch(() => "");
|
|
211
|
+
pdfError.value = `PDF error ${response.status}: ${text}`;
|
|
212
|
+
pdfDownloading.value = false;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const blob = await response.blob();
|
|
216
|
+
const url = URL.createObjectURL(blob);
|
|
217
|
+
const anchor = document.createElement("a");
|
|
218
|
+
anchor.href = url;
|
|
219
|
+
anchor.download = `${title.value}.pdf`;
|
|
220
|
+
anchor.click();
|
|
221
|
+
URL.revokeObjectURL(url);
|
|
222
|
+
pdfDownloading.value = false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function handleContentClick(event: MouseEvent) {
|
|
226
|
+
// 1. Internal wiki links: `[[Page Name]]` was rewritten to a
|
|
227
|
+
// `<span class="wiki-link">` during markdown pre-processing,
|
|
228
|
+
// so it doesn't overlap with regular `<a>` handling.
|
|
229
|
+
const target = event.target as HTMLElement;
|
|
230
|
+
const link = target.closest(".wiki-link") as HTMLElement | null;
|
|
231
|
+
if (link?.dataset.page) {
|
|
232
|
+
navigatePage(link.dataset.page);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// 2. External http(s) links in the rendered markdown body: open
|
|
236
|
+
// in a new tab so clicking them doesn't navigate the whole
|
|
237
|
+
// SPA away from MulmoClaude. Same-origin and non-http links
|
|
238
|
+
// (mailto:, tel:, anchors) fall through to the browser default.
|
|
239
|
+
handleExternalLinkClick(event);
|
|
240
|
+
}
|
|
241
|
+
</script>
|
|
242
|
+
|
|
243
|
+
<style scoped>
|
|
244
|
+
.wiki-content :deep(.wiki-link) {
|
|
245
|
+
color: #2563eb;
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
text-decoration: underline;
|
|
248
|
+
text-decoration-style: dotted;
|
|
249
|
+
}
|
|
250
|
+
.wiki-content :deep(.wiki-link:hover) {
|
|
251
|
+
text-decoration-style: solid;
|
|
252
|
+
}
|
|
253
|
+
.wiki-content :deep(h1) {
|
|
254
|
+
font-size: 1.5rem;
|
|
255
|
+
font-weight: 700;
|
|
256
|
+
margin-top: 1.5rem;
|
|
257
|
+
margin-bottom: 0.75rem;
|
|
258
|
+
color: #111827;
|
|
259
|
+
}
|
|
260
|
+
.wiki-content :deep(h2) {
|
|
261
|
+
font-size: 1.2rem;
|
|
262
|
+
font-weight: 600;
|
|
263
|
+
margin-top: 1.25rem;
|
|
264
|
+
margin-bottom: 0.5rem;
|
|
265
|
+
color: #1f2937;
|
|
266
|
+
border-bottom: 1px solid #e5e7eb;
|
|
267
|
+
padding-bottom: 0.25rem;
|
|
268
|
+
}
|
|
269
|
+
.wiki-content :deep(h3) {
|
|
270
|
+
font-size: 1rem;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
margin-top: 1rem;
|
|
273
|
+
margin-bottom: 0.5rem;
|
|
274
|
+
color: #374151;
|
|
275
|
+
}
|
|
276
|
+
.wiki-content :deep(p) {
|
|
277
|
+
margin-bottom: 0.75rem;
|
|
278
|
+
line-height: 1.6;
|
|
279
|
+
color: #374151;
|
|
280
|
+
}
|
|
281
|
+
.wiki-content :deep(ul),
|
|
282
|
+
.wiki-content :deep(ol) {
|
|
283
|
+
margin-left: 1.5rem;
|
|
284
|
+
margin-bottom: 0.75rem;
|
|
285
|
+
}
|
|
286
|
+
.wiki-content :deep(li) {
|
|
287
|
+
margin-bottom: 0.25rem;
|
|
288
|
+
line-height: 1.5;
|
|
289
|
+
color: #374151;
|
|
290
|
+
}
|
|
291
|
+
.wiki-content :deep(ul) {
|
|
292
|
+
list-style-type: disc;
|
|
293
|
+
}
|
|
294
|
+
.wiki-content :deep(ol) {
|
|
295
|
+
list-style-type: decimal;
|
|
296
|
+
}
|
|
297
|
+
.wiki-content :deep(hr) {
|
|
298
|
+
border: none;
|
|
299
|
+
border-top: 1px solid #e5e7eb;
|
|
300
|
+
margin: 1rem 0;
|
|
301
|
+
}
|
|
302
|
+
.wiki-content :deep(code) {
|
|
303
|
+
background: #f3f4f6;
|
|
304
|
+
padding: 0.1rem 0.3rem;
|
|
305
|
+
border-radius: 0.25rem;
|
|
306
|
+
font-size: 0.85em;
|
|
307
|
+
font-family: monospace;
|
|
308
|
+
}
|
|
309
|
+
.wiki-content :deep(pre) {
|
|
310
|
+
background: #f3f4f6;
|
|
311
|
+
padding: 0.75rem;
|
|
312
|
+
border-radius: 0.375rem;
|
|
313
|
+
overflow-x: auto;
|
|
314
|
+
margin-bottom: 0.75rem;
|
|
315
|
+
}
|
|
316
|
+
.wiki-content :deep(pre code) {
|
|
317
|
+
background: none;
|
|
318
|
+
padding: 0;
|
|
319
|
+
}
|
|
320
|
+
.wiki-content :deep(blockquote) {
|
|
321
|
+
border-left: 3px solid #d1d5db;
|
|
322
|
+
padding-left: 1rem;
|
|
323
|
+
color: #6b7280;
|
|
324
|
+
margin: 0.75rem 0;
|
|
325
|
+
}
|
|
326
|
+
.wiki-content :deep(table) {
|
|
327
|
+
border-collapse: collapse;
|
|
328
|
+
width: 100%;
|
|
329
|
+
margin-bottom: 0.75rem;
|
|
330
|
+
font-size: 0.875rem;
|
|
331
|
+
}
|
|
332
|
+
.wiki-content :deep(th),
|
|
333
|
+
.wiki-content :deep(td) {
|
|
334
|
+
border: 1px solid #e5e7eb;
|
|
335
|
+
padding: 0.5rem 0.75rem;
|
|
336
|
+
text-align: left;
|
|
337
|
+
}
|
|
338
|
+
.wiki-content :deep(th) {
|
|
339
|
+
background: #f9fafb;
|
|
340
|
+
font-weight: 600;
|
|
341
|
+
}
|
|
342
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ToolDefinition } from "gui-chat-protocol";
|
|
2
|
+
|
|
3
|
+
const toolDefinition: ToolDefinition = {
|
|
4
|
+
type: "function",
|
|
5
|
+
name: "manageWiki",
|
|
6
|
+
description:
|
|
7
|
+
"Display wiki content in the canvas — the index catalog, a specific page, the activity log, or a lint report. Call this whenever you want to show wiki content to the user.",
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
action: {
|
|
12
|
+
type: "string",
|
|
13
|
+
enum: ["index", "page", "log", "lint_report"],
|
|
14
|
+
description: "'index' = show the page catalog, 'page' = show a single page, 'log' = show activity log, 'lint_report' = run health check.",
|
|
15
|
+
},
|
|
16
|
+
pageName: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "For 'page' action: the page title or filename slug to display.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ["action"],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default toolDefinition;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Pure helpers for wiki/View.vue. Replaces the former
|
|
2
|
+
// `/\[\[([^\]]+)\]\]/g` regex — flagged by `sonarjs/slow-regex`
|
|
3
|
+
// for backtracking risk — with a linear walker.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Replace every `[[page name]]` occurrence in `content` with a
|
|
7
|
+
* `<span class="wiki-link" data-page="…">…</span>` element. The
|
|
8
|
+
* page name may not contain `]`; an opening `[[` that is not
|
|
9
|
+
* followed later by `]]` (with no bare `]` in between) is left
|
|
10
|
+
* untouched so malformed text renders as-is — matching the
|
|
11
|
+
* previous regex's non-match behaviour.
|
|
12
|
+
*
|
|
13
|
+
* The page-name text is used both as the `data-page` attribute
|
|
14
|
+
* value and as the span's visible text, identical to the old
|
|
15
|
+
* replacement string `'<span class="wiki-link" data-page="$1">$1</span>'`.
|
|
16
|
+
*/
|
|
17
|
+
export function renderWikiLinks(content: string): string {
|
|
18
|
+
const out: string[] = [];
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (i < content.length) {
|
|
21
|
+
if (content[i] === "[" && content[i + 1] === "[") {
|
|
22
|
+
const closeStart = findNextCloseBrackets(content, i + 2);
|
|
23
|
+
if (closeStart !== -1) {
|
|
24
|
+
const page = content.slice(i + 2, closeStart);
|
|
25
|
+
out.push(`<span class="wiki-link" data-page="${page}">${page}</span>`);
|
|
26
|
+
i = closeStart + 2;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
out.push(content[i]);
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
return out.join("");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Starting at `from`, scan forward for a `]]` sequence. Returns
|
|
38
|
+
* the index of the first `]` of that pair, or -1 if a bare `]`
|
|
39
|
+
* (one not immediately followed by a second `]`) is encountered
|
|
40
|
+
* first — mirroring the old regex's `[^\]]+` constraint that the
|
|
41
|
+
* page name must contain no `]` characters. Also returns -1 if
|
|
42
|
+
* nothing matched before the end of input, or if the pair sits
|
|
43
|
+
* immediately after `from` (zero-length page name, which the old
|
|
44
|
+
* regex rejected via the `+` quantifier).
|
|
45
|
+
*/
|
|
46
|
+
function findNextCloseBrackets(s: string, from: number): number {
|
|
47
|
+
let j = from;
|
|
48
|
+
while (j < s.length) {
|
|
49
|
+
if (s[j] === "]") {
|
|
50
|
+
if (s[j + 1] === "]" && j > from) return j;
|
|
51
|
+
// Bare `]` inside the page-name span — old regex would not
|
|
52
|
+
// match here, so we bail and let the caller emit the `[[`
|
|
53
|
+
// as literal text.
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
56
|
+
j++;
|
|
57
|
+
}
|
|
58
|
+
return -1;
|
|
59
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ToolPlugin } from "../../tools/types";
|
|
2
|
+
import type { ToolResult } from "gui-chat-protocol";
|
|
3
|
+
import View from "./View.vue";
|
|
4
|
+
import Preview from "./Preview.vue";
|
|
5
|
+
import toolDefinition from "./definition";
|
|
6
|
+
import { apiPost } from "../../utils/api";
|
|
7
|
+
import { API_ROUTES } from "../../config/apiRoutes";
|
|
8
|
+
|
|
9
|
+
export interface WikiPageEntry {
|
|
10
|
+
title: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WikiData {
|
|
16
|
+
action: string;
|
|
17
|
+
title: string;
|
|
18
|
+
content: string;
|
|
19
|
+
pageEntries?: WikiPageEntry[];
|
|
20
|
+
pageName?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const wikiPlugin: ToolPlugin<WikiData> = {
|
|
24
|
+
toolDefinition,
|
|
25
|
+
|
|
26
|
+
async execute(_context, args) {
|
|
27
|
+
const result = await apiPost<ToolResult<WikiData>>(API_ROUTES.wiki.base, args);
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
// Return an error ToolResult instead of throwing so execute()
|
|
30
|
+
// stays symmetric with every other plugin in this repo. Callers
|
|
31
|
+
// can branch on the `error` field uniformly.
|
|
32
|
+
const prefix = result.status === 0 ? "Wiki request failed" : `Wiki API error ${result.status}`;
|
|
33
|
+
return {
|
|
34
|
+
toolName: "manageWiki",
|
|
35
|
+
uuid: crypto.randomUUID(),
|
|
36
|
+
message: `${prefix}: ${result.error}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...result.data,
|
|
41
|
+
toolName: "manageWiki",
|
|
42
|
+
uuid: result.data.uuid ?? crypto.randomUUID(),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
isEnabled: () => true,
|
|
47
|
+
generatingMessage: "Loading wiki...",
|
|
48
|
+
viewComponent: View,
|
|
49
|
+
previewComponent: Preview,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default wikiPlugin;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Navigation guard: validates and sanitizes URL parameters.
|
|
2
|
+
//
|
|
3
|
+
// All incoming route params / query values are untrusted — they could
|
|
4
|
+
// come from a user-typed URL, a pasted link, or a malicious redirect.
|
|
5
|
+
// This guard runs before every navigation and rewrites invalid state
|
|
6
|
+
// to safe defaults without the user noticing (router.replace, which
|
|
7
|
+
// doesn't push a history entry).
|
|
8
|
+
|
|
9
|
+
import type { Router } from "vue-router";
|
|
10
|
+
import { VALID_VIEW_MODES } from "../utils/canvas/viewMode";
|
|
11
|
+
|
|
12
|
+
// Basic sanity check for a session ID. Real existence verification
|
|
13
|
+
// happens in App.vue's onMounted / loadSession — we can't do async
|
|
14
|
+
// server calls in a synchronous beforeEach guard without making
|
|
15
|
+
// every navigation await a fetch. Instead, reject values that are
|
|
16
|
+
// obviously malicious (HTML tags, extremely long, contain path
|
|
17
|
+
// separators) and let the app-level code handle "valid format but
|
|
18
|
+
// doesn't exist on the server" gracefully.
|
|
19
|
+
const SESSION_ID_RE = /^[\w-]{1,128}$/;
|
|
20
|
+
|
|
21
|
+
function isValidSessionId(id: unknown): boolean {
|
|
22
|
+
return typeof id === "string" && SESSION_ID_RE.test(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function installGuards(router: Router): void {
|
|
26
|
+
router.beforeEach((to) => {
|
|
27
|
+
// Only run guards on the chat route — other routes (redirect, etc.)
|
|
28
|
+
// don't carry parameters that need sanitizing.
|
|
29
|
+
if (to.name !== "chat") return;
|
|
30
|
+
|
|
31
|
+
// ── sessionId format check ───────────────────────────────────
|
|
32
|
+
const sessionId = to.params.sessionId;
|
|
33
|
+
if (typeof sessionId === "string" && sessionId.length > 0 && !isValidSessionId(sessionId)) {
|
|
34
|
+
// Garbage sessionId → strip it and go to /chat (new session).
|
|
35
|
+
return { name: "chat", params: {}, query: {}, replace: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── view mode whitelist ──────────────────────────────────────
|
|
39
|
+
const view = to.query.view;
|
|
40
|
+
if (typeof view === "string" && !VALID_VIEW_MODES.has(view)) {
|
|
41
|
+
const cleaned = { ...to.query };
|
|
42
|
+
delete cleaned.view;
|
|
43
|
+
return { ...to, query: cleaned, replace: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── file path traversal check ────────────────────────────────
|
|
47
|
+
const filePath = to.query.path;
|
|
48
|
+
if (typeof filePath === "string") {
|
|
49
|
+
if (filePath.includes("..") || filePath.startsWith("/")) {
|
|
50
|
+
const cleaned = { ...to.query };
|
|
51
|
+
delete cleaned.path;
|
|
52
|
+
return { ...to, query: cleaned, replace: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ?path= without ?view=files → auto-add view=files so FilesView mounts.
|
|
56
|
+
if (view !== "files") {
|
|
57
|
+
return { ...to, query: { ...to.query, view: "files" }, replace: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Vue-router setup (history mode — clean URLs without #).
|
|
2
|
+
//
|
|
3
|
+
// The route is /chat/:sessionId? which captures the session the user
|
|
4
|
+
// was looking at. Everything else (view mode, file path, result uuid,
|
|
5
|
+
// role) lives in query parameters and will be wired in later phases.
|
|
6
|
+
//
|
|
7
|
+
// The "/" → "/chat" redirect ensures a fresh browser tab always lands
|
|
8
|
+
// on the chat view with the default (new) session, matching the
|
|
9
|
+
// current pre-router behaviour.
|
|
10
|
+
//
|
|
11
|
+
// History mode requires the server to serve index.html for any path
|
|
12
|
+
// that doesn't match an API route or static file. In production the
|
|
13
|
+
// Express catch-all `app.get("*", ...)` in server/index.ts already
|
|
14
|
+
// does this. In dev, Vite's default SPA fallback handles it.
|
|
15
|
+
|
|
16
|
+
import { defineComponent, h } from "vue";
|
|
17
|
+
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
|
|
18
|
+
|
|
19
|
+
// Stub component that renders nothing. Required by vue-router (every
|
|
20
|
+
// route needs a component) but never actually mounted because App.vue
|
|
21
|
+
// doesn't contain <router-view> in Phase 0.
|
|
22
|
+
const Stub = defineComponent({ render: () => h("div") });
|
|
23
|
+
|
|
24
|
+
const routes: RouteRecordRaw[] = [
|
|
25
|
+
{
|
|
26
|
+
path: "/",
|
|
27
|
+
redirect: "/chat",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
// sessionId is optional — /chat with no param means "new session"
|
|
31
|
+
// (the App.vue logic that auto-creates a session continues to
|
|
32
|
+
// handle this).
|
|
33
|
+
path: "/chat/:sessionId?",
|
|
34
|
+
name: "chat",
|
|
35
|
+
component: Stub,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
// Catch-all: unknown paths redirect to /chat so a stale bookmark
|
|
39
|
+
// or a typo doesn't show a blank page.
|
|
40
|
+
path: "/:pathMatch(.*)*",
|
|
41
|
+
redirect: "/chat",
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const router = createRouter({
|
|
46
|
+
history: createWebHistory(),
|
|
47
|
+
routes,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export default router;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PluginEntry } from "./types";
|
|
2
|
+
import textResponsePlugin from "../plugins/textResponse/index";
|
|
3
|
+
import markdownPlugin from "../plugins/markdown/index";
|
|
4
|
+
import spreadsheetPlugin from "../plugins/spreadsheet/index";
|
|
5
|
+
import MindMapPlugin from "@gui-chat-plugin/mindmap/vue";
|
|
6
|
+
import generateImagePlugin from "../plugins/generateImage/index";
|
|
7
|
+
import QuizPlugin from "@mulmochat-plugin/quiz/vue";
|
|
8
|
+
import FormPlugin from "@mulmochat-plugin/form/vue";
|
|
9
|
+
import canvasPlugin from "../plugins/canvas/index";
|
|
10
|
+
import editImagePlugin from "../plugins/editImage/index";
|
|
11
|
+
import Present3DPlugin from "@gui-chat-plugin/present3d/vue";
|
|
12
|
+
import WeatherPlugin from "@gui-chat-plugin/weather/vue";
|
|
13
|
+
import todoPlugin from "../plugins/todo/index";
|
|
14
|
+
import schedulerPlugin from "../plugins/scheduler/index";
|
|
15
|
+
import manageRolesPlugin from "../plugins/manageRoles/index";
|
|
16
|
+
import manageSkillsPlugin from "../plugins/manageSkills/index";
|
|
17
|
+
import manageSourcePlugin from "../plugins/manageSource/index";
|
|
18
|
+
import wikiPlugin from "../plugins/wiki/index";
|
|
19
|
+
import presentMulmoScriptPlugin from "../plugins/presentMulmoScript/index";
|
|
20
|
+
import presentHtmlPlugin from "../plugins/presentHtml/index";
|
|
21
|
+
import presentChartPlugin from "../plugins/chart/index";
|
|
22
|
+
|
|
23
|
+
const plugins: Record<string, PluginEntry> = {
|
|
24
|
+
"text-response": textResponsePlugin.plugin,
|
|
25
|
+
manageTodoList: todoPlugin,
|
|
26
|
+
manageScheduler: schedulerPlugin,
|
|
27
|
+
manageRoles: manageRolesPlugin,
|
|
28
|
+
manageSkills: manageSkillsPlugin,
|
|
29
|
+
manageSource: manageSourcePlugin,
|
|
30
|
+
manageWiki: wikiPlugin,
|
|
31
|
+
presentMulmoScript: presentMulmoScriptPlugin,
|
|
32
|
+
presentDocument: markdownPlugin,
|
|
33
|
+
presentSpreadsheet: spreadsheetPlugin,
|
|
34
|
+
createMindMap: MindMapPlugin.plugin,
|
|
35
|
+
generateImage: generateImagePlugin,
|
|
36
|
+
putQuestions: QuizPlugin.plugin,
|
|
37
|
+
presentForm: FormPlugin.plugin,
|
|
38
|
+
openCanvas: canvasPlugin,
|
|
39
|
+
presentHtml: presentHtmlPlugin,
|
|
40
|
+
presentChart: presentChartPlugin,
|
|
41
|
+
editImage: editImagePlugin,
|
|
42
|
+
present3D: Present3DPlugin.plugin,
|
|
43
|
+
weather: WeatherPlugin.plugin,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function getPlugin(name: string): PluginEntry | null {
|
|
47
|
+
return plugins[name] ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getAllPluginNames(): string[] {
|
|
51
|
+
return Object.keys(plugins);
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ToolPlugin as BaseToolPlugin, InputHandler, ToolContextApp, ToolDefinition } from "gui-chat-protocol/vue";
|
|
2
|
+
import type { Component } from "vue";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extended app context with file system access for workspace-aware plugins
|
|
6
|
+
*/
|
|
7
|
+
export interface MulmoClaudeToolContextApp extends ToolContextApp {
|
|
8
|
+
readFile: (path: string) => Promise<string>;
|
|
9
|
+
writeFile: (path: string, content: string) => Promise<void>;
|
|
10
|
+
workspacePath: () => string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MulmoClaude ToolPlugin — no app-specific server response type needed
|
|
15
|
+
*/
|
|
16
|
+
export type ToolPlugin<T = unknown, J = unknown, A extends object = object> = BaseToolPlugin<T, J, A, InputHandler, Record<string, unknown>>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* View-only plugin entry for the frontend registry.
|
|
20
|
+
* Only the properties actually used on the client side are required.
|
|
21
|
+
* This avoids contravariance issues with execute's args type parameter.
|
|
22
|
+
*/
|
|
23
|
+
export interface PluginEntry {
|
|
24
|
+
toolDefinition: ToolDefinition;
|
|
25
|
+
viewComponent?: Component;
|
|
26
|
+
previewComponent?: Component;
|
|
27
|
+
}
|