mulmoclaude 0.1.2 → 0.4.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/bin/mulmoclaude.js +7 -24
- package/client/assets/html2canvas-Cx501zZr-Cv5snK9D.js +5 -0
- package/client/assets/index-CubzmCVK.css +2 -0
- package/client/assets/{index-D8rhwXLq.js → index-DtcyExH9.js} +80 -61
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-DnizuhIY.js} +5 -5
- package/client/index.html +2 -4
- package/package.json +13 -13
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/config.ts +12 -12
- package/server/agent/index.ts +9 -3
- package/server/agent/mcp-server.ts +19 -19
- package/server/agent/mcp-tools/index.ts +6 -6
- package/server/agent/mcp-tools/x.ts +7 -6
- package/server/agent/prompt.ts +195 -29
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +10 -10
- package/server/agent/stream.ts +4 -4
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +21 -3
- package/server/api/routes/config.ts +1 -1
- package/server/api/routes/files.ts +22 -21
- package/server/api/routes/html.ts +2 -2
- package/server/api/routes/image.ts +7 -7
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +16 -6
- package/server/api/routes/roles.ts +2 -2
- package/server/api/routes/scheduler.ts +14 -12
- package/server/api/routes/schedulerHandlers.ts +12 -12
- package/server/api/routes/schedulerTasks.ts +19 -17
- package/server/api/routes/sessions.ts +26 -26
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +5 -5
- package/server/api/routes/sources.ts +3 -3
- package/server/api/routes/todosColumnsHandlers.ts +30 -30
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki.ts +36 -22
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +6 -6
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +17 -16
- package/server/events/scheduler-adapter.ts +20 -20
- package/server/events/session-store/index.ts +10 -10
- package/server/events/task-manager/index.ts +7 -7
- package/server/index.ts +59 -65
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +5 -5
- package/server/utils/date.ts +18 -18
- package/server/utils/files/atomic.ts +16 -16
- package/server/utils/files/html-io.ts +5 -5
- package/server/utils/files/image-store.ts +19 -8
- package/server/utils/files/journal-io.ts +4 -4
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-store.ts +4 -4
- package/server/utils/files/naming.ts +2 -2
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +12 -12
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-io.ts +5 -5
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/session-io.ts +35 -35
- package/server/utils/files/spreadsheet-store.ts +7 -7
- package/server/utils/files/todos-io.ts +9 -9
- package/server/utils/files/user-tasks-io.ts +5 -5
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +2 -2
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- package/server/utils/markdown.ts +5 -5
- package/server/utils/port.d.mts +6 -0
- package/server/utils/port.mjs +48 -0
- package/server/utils/request.ts +12 -6
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/indexer.ts +15 -15
- package/server/workspace/chat-index/summarizer.ts +4 -4
- package/server/workspace/custom-dirs.ts +16 -16
- package/server/workspace/journal/archivist.ts +35 -35
- package/server/workspace/journal/dailyPass.ts +31 -28
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +4 -4
- package/server/workspace/journal/indexFile.ts +29 -25
- package/server/workspace/journal/optimizationPass.ts +2 -2
- package/server/workspace/journal/state.ts +6 -6
- package/server/workspace/paths.ts +3 -3
- package/server/workspace/reference-dirs.ts +20 -20
- package/server/workspace/roles.ts +6 -6
- package/server/workspace/skills/discovery.ts +4 -4
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +3 -3
- package/server/workspace/skills/user-tasks.ts +34 -34
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +10 -10
- package/server/workspace/sources/classifier.ts +7 -7
- package/server/workspace/sources/fetchers/arxiv.ts +7 -7
- package/server/workspace/sources/fetchers/githubIssues.ts +7 -7
- package/server/workspace/sources/fetchers/githubReleases.ts +7 -7
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +12 -12
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +36 -13
- package/server/workspace/sources/pipeline/index.ts +8 -13
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/plan.ts +15 -13
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/registry.ts +16 -16
- package/server/workspace/sources/robots.ts +14 -14
- package/server/workspace/sources/sourceState.ts +17 -10
- package/server/workspace/sources/types.ts +9 -0
- package/server/workspace/sources/urls.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +4 -4
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +26 -16
- package/server/workspace/wiki-backlinks/index.ts +8 -8
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +15 -15
- package/server/workspace/workspace.ts +7 -7
- package/src/App.vue +315 -141
- package/src/components/CanvasViewToggle.vue +10 -7
- package/src/components/ChatInput.vue +67 -33
- package/src/components/FileContentHeader.vue +7 -4
- package/src/components/FileContentRenderer.vue +20 -6
- package/src/components/FileTree.vue +6 -3
- package/src/components/FileTreePane.vue +11 -8
- package/src/components/FilesView.vue +5 -3
- package/src/components/LockStatusPopup.vue +17 -14
- package/src/components/NotificationBell.vue +14 -5
- package/src/components/NotificationToast.vue +6 -3
- package/src/components/PluginLauncher.vue +19 -56
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/RoleSelector.vue +2 -2
- package/src/components/SessionHistoryPanel.vue +38 -34
- package/src/components/SessionTabBar.vue +8 -10
- package/src/components/SettingsMcpTab.vue +49 -36
- package/src/components/SettingsModal.vue +24 -22
- package/src/components/SettingsReferenceDirsTab.vue +39 -34
- package/src/components/SettingsWorkspaceDirsTab.vue +37 -27
- package/src/components/SidebarHeader.vue +25 -4
- package/src/components/StackView.vue +4 -1
- package/src/components/SuggestionsPanel.vue +7 -4
- package/src/components/TodoExplorer.vue +26 -15
- package/src/components/ToolResultsPanel.vue +27 -13
- package/src/components/todo/TodoAddDialog.vue +19 -14
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +17 -12
- package/src/components/todo/TodoKanbanView.vue +10 -5
- package/src/components/todo/TodoListView.vue +10 -7
- package/src/components/todo/TodoTableView.vue +5 -2
- package/src/composables/useAppApi.ts +9 -0
- package/src/composables/useClickOutside.ts +2 -2
- package/src/composables/useDynamicFavicon.ts +172 -37
- package/src/composables/useEventListeners.ts +7 -8
- package/src/composables/useFaviconState.ts +13 -2
- package/src/composables/useFileSelection.ts +24 -6
- package/src/composables/useFreshPluginData.ts +3 -3
- package/src/composables/useKeyNavigation.ts +11 -11
- package/src/composables/useLayoutMode.ts +32 -0
- package/src/composables/useMcpTools.ts +2 -2
- package/src/composables/useNotifications.ts +3 -3
- package/src/composables/usePdfDownload.ts +4 -4
- package/src/composables/usePendingCalls.ts +1 -1
- package/src/composables/usePubSub.ts +10 -10
- package/src/composables/useRoles.ts +1 -1
- package/src/composables/useSandboxStatus.ts +1 -1
- package/src/composables/useSessionDerived.ts +3 -3
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useSessionSync.ts +8 -8
- package/src/composables/useViewLayout.ts +20 -34
- package/src/config/roles.ts +2 -2
- package/src/lang/de.ts +536 -0
- package/src/lang/en.ts +558 -0
- package/src/lang/es.ts +543 -0
- package/src/lang/fr.ts +536 -0
- package/src/lang/ja.ts +536 -0
- package/src/lang/ko.ts +540 -0
- package/src/lang/pt-BR.ts +534 -0
- package/src/lang/zh.ts +537 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +2 -0
- package/src/plugins/canvas/View.vue +102 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/chart/Preview.vue +5 -5
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +59 -43
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +29 -25
- package/src/plugins/manageSource/Preview.vue +2 -2
- package/src/plugins/manageSource/View.vue +73 -52
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +26 -36
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentHtml/helpers.ts +8 -8
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +40 -30
- package/src/plugins/presentMulmoScript/helpers.ts +1 -1
- package/src/plugins/scheduler/Preview.vue +13 -10
- package/src/plugins/scheduler/TasksTab.vue +57 -28
- package/src/plugins/scheduler/View.vue +28 -19
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/spreadsheet/Preview.vue +8 -3
- package/src/plugins/spreadsheet/View.vue +21 -12
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +29 -9
- package/src/plugins/todo/Preview.vue +13 -8
- package/src/plugins/todo/View.vue +38 -24
- package/src/plugins/todo/composables/useTodos.ts +5 -5
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +10 -7
- package/src/plugins/wiki/View.vue +202 -81
- package/src/plugins/wiki/helpers.ts +4 -4
- package/src/plugins/wiki/route.ts +112 -0
- package/src/router/guards.ts +46 -28
- package/src/router/index.ts +41 -26
- package/src/types/session.ts +4 -3
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/request.ts +22 -3
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/dom/scrollable.ts +2 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/files/sortChildren.ts +6 -6
- package/src/utils/format/frontmatter.ts +6 -6
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +5 -5
- package/src/utils/markdown/extractFirstH1.ts +2 -2
- package/src/utils/path/relativeLink.ts +15 -15
- package/src/utils/path/workspaceLinkRouter.ts +81 -0
- package/src/utils/role/icon.ts +2 -2
- package/src/utils/role/merge.ts +2 -2
- package/src/utils/role/plugins.ts +1 -1
- package/src/utils/session/sessionFactory.ts +2 -2
- package/src/utils/session/sessionHelpers.ts +2 -2
- package/src/utils/tools/dedup.ts +4 -4
- package/src/utils/tools/result.ts +3 -3
- package/src/utils/types.ts +2 -2
- package/src/vite-env.d.ts +9 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +0 -1
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +0 -5
- package/client/assets/index-KNLBjwuh.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/utils/canvas/viewMode.ts +0 -46
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
package/client/index.html
CHANGED
|
@@ -17,10 +17,8 @@
|
|
|
17
17
|
<title>MulmoClaude</title>
|
|
18
18
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='30' height='30' x='1' y='1' rx='6' fill='%236B7280'/><text x='16' y='17' text-anchor='middle' dominant-baseline='central' font-family='sans-serif' font-weight='bold' font-size='20' fill='white'>M</text></svg>" />
|
|
19
19
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
20
|
-
<script type="module" crossorigin src="/assets/index-
|
|
21
|
-
<link rel="
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/typeof-DBp4T-Ny-BC0P-2DM.js">
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/assets/index-KNLBjwuh.css">
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-DtcyExH9.js"></script>
|
|
21
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CubzmCVK.css">
|
|
24
22
|
</head>
|
|
25
23
|
<body>
|
|
26
24
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mulmoclaude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MulmoClaude — GUI-chat with Claude Code + long-term memory. One command to start.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,20 +18,20 @@
|
|
|
18
18
|
"src/"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@mulmobridge/chat-service": "^0.1.1",
|
|
22
|
-
"@mulmobridge/client": "^0.1.1",
|
|
23
|
-
"@mulmobridge/protocol": "^0.1.3",
|
|
24
|
-
"@receptron/task-scheduler": "^0.1.0",
|
|
25
21
|
"@google/genai": "^1.50.1",
|
|
26
|
-
"@mulmocast/types": "^2.6.7",
|
|
27
|
-
"@gui-chat-plugin/mindmap": "^0.4.0",
|
|
28
|
-
"@gui-chat-plugin/present3d": "^0.1.0",
|
|
29
22
|
"@gui-chat-plugin/browse": "^0.2.0",
|
|
30
23
|
"@gui-chat-plugin/camera": "^0.4.0",
|
|
24
|
+
"@gui-chat-plugin/mindmap": "^0.4.0",
|
|
25
|
+
"@gui-chat-plugin/present3d": "^0.1.0",
|
|
31
26
|
"@gui-chat-plugin/weather": "^0.1.0",
|
|
27
|
+
"@mulmobridge/chat-service": "^0.1.1",
|
|
28
|
+
"@mulmobridge/client": "^0.1.1",
|
|
29
|
+
"@mulmobridge/protocol": "^0.1.3",
|
|
30
|
+
"@mulmocast/types": "^2.6.8",
|
|
32
31
|
"@mulmochat-plugin/form": "0.5.0",
|
|
33
32
|
"@mulmochat-plugin/quiz": "0.4.0",
|
|
34
33
|
"@mulmochat-plugin/ui-image": "^0.3.0",
|
|
34
|
+
"@receptron/task-scheduler": "^0.1.0",
|
|
35
35
|
"cors": "^2.8.6",
|
|
36
36
|
"dotenv": "^17.4.2",
|
|
37
37
|
"express": "^5.2.1",
|
|
@@ -40,15 +40,15 @@
|
|
|
40
40
|
"ignore": "^7.0.5",
|
|
41
41
|
"mammoth": "^1.12.0",
|
|
42
42
|
"marked": "^18.0.2",
|
|
43
|
-
"mulmocast": "^2.6.
|
|
44
|
-
"puppeteer": "^24.
|
|
43
|
+
"mulmocast": "^2.6.8",
|
|
44
|
+
"puppeteer": "^24.42.0",
|
|
45
45
|
"socket.io": "^4.8.3",
|
|
46
46
|
"socket.io-client": "^4.8.3",
|
|
47
|
-
"
|
|
47
|
+
"tsx": "^4.19.0",
|
|
48
|
+
"uuid": "^14.0.0",
|
|
48
49
|
"ws": "^8.20.0",
|
|
49
50
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
|
50
|
-
"zod": "^4.3.6"
|
|
51
|
-
"tsx": "^4.19.0"
|
|
51
|
+
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=20"
|
|
@@ -17,7 +17,7 @@ import * as XLSX from "xlsx";
|
|
|
17
17
|
import { execFile } from "child_process";
|
|
18
18
|
import { mkdtemp, readFile, writeFile, rm } from "fs/promises";
|
|
19
19
|
import path from "path";
|
|
20
|
-
import
|
|
20
|
+
import { tmpdir } from "os";
|
|
21
21
|
import { promisify } from "util";
|
|
22
22
|
|
|
23
23
|
const execFileAsync = promisify(execFile);
|
|
@@ -116,7 +116,7 @@ async function tryDockerLibreOffice(): Promise<boolean> {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
async function convertPptxToPdf(data: string): Promise<Buffer | null> {
|
|
119
|
-
const tmpDir = await mkdtemp(path.join(
|
|
119
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "pptx-"));
|
|
120
120
|
const inputPath = path.join(tmpDir, "input.pptx");
|
|
121
121
|
const outputPath = path.join(tmpDir, "input.pdf");
|
|
122
122
|
|
package/server/agent/config.ts
CHANGED
|
@@ -14,10 +14,10 @@ export const CONTAINER_WORKSPACE_PATH = "/home/node/mulmoclaude";
|
|
|
14
14
|
|
|
15
15
|
const BASE_ALLOWED_TOOLS = ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch"];
|
|
16
16
|
|
|
17
|
-
const MCP_PLUGINS = new Set([...MCP_PLUGIN_NAMES, ...mcpTools.filter(isMcpToolEnabled).map((
|
|
17
|
+
const MCP_PLUGINS = new Set([...MCP_PLUGIN_NAMES, ...mcpTools.filter(isMcpToolEnabled).map((toolDef) => toolDef.definition.name)]);
|
|
18
18
|
|
|
19
19
|
export function getActivePlugins(role: Role): string[] {
|
|
20
|
-
return role.availablePlugins.filter((
|
|
20
|
+
return role.availablePlugins.filter((pluginName) => MCP_PLUGINS.has(pluginName));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface McpConfigParams {
|
|
@@ -71,12 +71,12 @@ function prepareUserStdioServer(spec: Extract<McpServerSpec, { type: "stdio" }>,
|
|
|
71
71
|
|
|
72
72
|
export function prepareUserServers(userServers: Record<string, McpServerSpec>, useDocker: boolean, hostWorkspacePath: string): Record<string, McpServerSpec> {
|
|
73
73
|
const out: Record<string, McpServerSpec> = {};
|
|
74
|
-
for (const [
|
|
74
|
+
for (const [serverId, spec] of Object.entries(userServers)) {
|
|
75
75
|
if (spec.enabled === false) continue;
|
|
76
76
|
if (spec.type === "http") {
|
|
77
|
-
out[
|
|
77
|
+
out[serverId] = prepareUserHttpServer(spec, useDocker);
|
|
78
78
|
} else {
|
|
79
|
-
out[
|
|
79
|
+
out[serverId] = prepareUserStdioServer(spec, useDocker, hostWorkspacePath);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
return out;
|
|
@@ -137,9 +137,9 @@ function buildMulmoclaudeServer(params: { chatSessionId: string; port: number; a
|
|
|
137
137
|
// defence-in-depth.
|
|
138
138
|
function excludeReservedKeys(servers: Record<string, McpServerSpec>): Record<string, McpServerSpec> {
|
|
139
139
|
const out: Record<string, McpServerSpec> = {};
|
|
140
|
-
for (const [
|
|
141
|
-
if (
|
|
142
|
-
out[
|
|
140
|
+
for (const [serverId, spec] of Object.entries(servers)) {
|
|
141
|
+
if (serverId === "mulmoclaude") continue;
|
|
142
|
+
out[serverId] = spec;
|
|
143
143
|
}
|
|
144
144
|
return out;
|
|
145
145
|
}
|
|
@@ -165,12 +165,12 @@ export function buildMcpConfig(params: McpConfigParams): object {
|
|
|
165
165
|
// we're running natively (since the sandbox image is minimal in Docker).
|
|
166
166
|
export function userServerAllowedToolNames(userServers: Record<string, McpServerSpec>, useDocker: boolean): string[] {
|
|
167
167
|
const names: string[] = [];
|
|
168
|
-
for (const [
|
|
168
|
+
for (const [serverId, spec] of Object.entries(userServers)) {
|
|
169
169
|
if (spec.enabled === false) continue;
|
|
170
170
|
// Stdio servers are dropped under Docker because the sandbox
|
|
171
171
|
// image is too minimal to run most of them (see #162).
|
|
172
172
|
if (spec.type === "stdio" && useDocker) continue;
|
|
173
|
-
names.push(`mcp__${
|
|
173
|
+
names.push(`mcp__${serverId}`);
|
|
174
174
|
}
|
|
175
175
|
return names;
|
|
176
176
|
}
|
|
@@ -188,7 +188,7 @@ export interface CliArgsParams {
|
|
|
188
188
|
export function buildCliArgs(params: CliArgsParams): string[] {
|
|
189
189
|
const { systemPrompt, activePlugins, claudeSessionId, mcpConfigPath, extraAllowedTools = [] } = params;
|
|
190
190
|
|
|
191
|
-
const mcpToolNames = activePlugins.map((
|
|
191
|
+
const mcpToolNames = activePlugins.map((pluginName) => `mcp__mulmoclaude__${pluginName}`);
|
|
192
192
|
const allowedTools = [...BASE_ALLOWED_TOOLS, ...extraAllowedTools, ...mcpToolNames];
|
|
193
193
|
|
|
194
194
|
// stream-json input mode: the user message is streamed through
|
|
@@ -351,7 +351,7 @@ export function buildDockerSpawnArgs(params: DockerSpawnArgsParams): string[] {
|
|
|
351
351
|
sandboxAuthArgs = [],
|
|
352
352
|
sshAgentForward = false,
|
|
353
353
|
} = params;
|
|
354
|
-
const toDockerPath = (
|
|
354
|
+
const toDockerPath = (hostPath: string): string => hostPath.replace(/\\/g, "/");
|
|
355
355
|
const extraHosts: string[] = platform === "linux" ? ["--add-host", "host.docker.internal:host-gateway"] : [];
|
|
356
356
|
|
|
357
357
|
return [
|
package/server/agent/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, type ChildProcessByStdio } from "child_process";
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, unlink } from "fs/promises";
|
|
3
|
+
import { writeJsonAtomic } from "../utils/files/json.js";
|
|
3
4
|
import { dirname } from "path";
|
|
4
5
|
import type { Readable, Writable } from "stream";
|
|
5
6
|
import { isDockerAvailable } from "../system/docker.js";
|
|
@@ -157,6 +158,7 @@ export async function* runAgent(
|
|
|
157
158
|
claudeSessionId?: string,
|
|
158
159
|
abortSignal?: AbortSignal,
|
|
159
160
|
attachments?: Attachment[],
|
|
161
|
+
userTimezone?: string,
|
|
160
162
|
): AsyncGenerator<AgentEvent> {
|
|
161
163
|
const activePlugins = getActivePlugins(role);
|
|
162
164
|
const useDocker = await isDockerAvailable();
|
|
@@ -180,6 +182,7 @@ export async function* runAgent(
|
|
|
180
182
|
role,
|
|
181
183
|
workspacePath: useDocker ? CONTAINER_WORKSPACE_PATH : workspacePath,
|
|
182
184
|
useDocker,
|
|
185
|
+
userTimezone,
|
|
183
186
|
});
|
|
184
187
|
|
|
185
188
|
// In debug mode (--debug), dump the full system prompt on the first
|
|
@@ -202,11 +205,14 @@ export async function* runAgent(
|
|
|
202
205
|
chatSessionId: sessionId,
|
|
203
206
|
port,
|
|
204
207
|
activePlugins,
|
|
205
|
-
roleIds: loadAllRoles().map((
|
|
208
|
+
roleIds: loadAllRoles().map((loadedRole) => loadedRole.id),
|
|
206
209
|
useDocker,
|
|
207
210
|
userServers,
|
|
208
211
|
});
|
|
209
|
-
|
|
212
|
+
// Write atomically so a partially-written file can't be picked
|
|
213
|
+
// up by a concurrent claude spawn (they share the --mcp-config
|
|
214
|
+
// path under the session dir).
|
|
215
|
+
await writeJsonAtomic(mcpPaths.hostPath, mcpConfig);
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
// Fresh read on every invocation so the Settings UI can change
|
|
@@ -30,7 +30,7 @@ interface JsonRpcMessage {
|
|
|
30
30
|
params?: ToolCallParams;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const isJsonRpcMessage = (
|
|
33
|
+
const isJsonRpcMessage = (value: unknown): value is JsonRpcMessage => isRecord(value) && "method" in value;
|
|
34
34
|
|
|
35
35
|
const SESSION_ID = env.mcpSessionId;
|
|
36
36
|
const PORT = env.port;
|
|
@@ -80,12 +80,12 @@ function fromPackage(def: ToolDefinition, endpoint: string): ToolDef {
|
|
|
80
80
|
|
|
81
81
|
// Pure MCP tools (no GUI) — auto-registered from server/mcp-tools/
|
|
82
82
|
const mcpToolDefs: Record<string, ToolDef> = Object.fromEntries(
|
|
83
|
-
mcpTools.filter(isMcpToolEnabled).map((
|
|
84
|
-
|
|
83
|
+
mcpTools.filter(isMcpToolEnabled).map((toolDef) => [
|
|
84
|
+
toolDef.definition.name,
|
|
85
85
|
{
|
|
86
|
-
name:
|
|
87
|
-
description:
|
|
88
|
-
inputSchema:
|
|
86
|
+
name: toolDef.definition.name,
|
|
87
|
+
description: toolDef.definition.description,
|
|
88
|
+
inputSchema: toolDef.definition.inputSchema,
|
|
89
89
|
},
|
|
90
90
|
]),
|
|
91
91
|
);
|
|
@@ -303,7 +303,7 @@ async function handleToolCall(name: string, args: Record<string, unknown>): Prom
|
|
|
303
303
|
// Pure MCP tools — call via /api/mcp-tools/:tool, return text directly
|
|
304
304
|
// (no frontend push). Opt out of postJson's HTTP error throw because
|
|
305
305
|
// we want to surface the JSON error body to the caller as a string.
|
|
306
|
-
const mcpTool = mcpTools.find((
|
|
306
|
+
const mcpTool = mcpTools.find((toolDef) => toolDef.definition.name === name);
|
|
307
307
|
if (mcpTool) {
|
|
308
308
|
const res = await postJson(`/api/mcp-tools/${name}`, args, {
|
|
309
309
|
allowHttpError: true,
|
|
@@ -313,7 +313,7 @@ async function handleToolCall(name: string, args: Record<string, unknown>): Prom
|
|
|
313
313
|
return typeof json.result === "string" ? json.result : JSON.stringify(json.result);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
const tool = tools.find((
|
|
316
|
+
const tool = tools.find((toolDef) => toolDef.name === name);
|
|
317
317
|
if (!tool) throw new Error(`Unknown tool: ${name}`);
|
|
318
318
|
|
|
319
319
|
const res = await postJson(tool.endpoint!, args);
|
|
@@ -347,12 +347,12 @@ process.stdin.on("data", (chunk: Buffer) => {
|
|
|
347
347
|
}
|
|
348
348
|
if (!isJsonRpcMessage(msg)) continue;
|
|
349
349
|
|
|
350
|
-
const { id, method, params } = msg;
|
|
350
|
+
const { id: requestId, method, params } = msg;
|
|
351
351
|
|
|
352
352
|
if (method === "initialize") {
|
|
353
353
|
respond({
|
|
354
354
|
jsonrpc: "2.0",
|
|
355
|
-
id,
|
|
355
|
+
id: requestId,
|
|
356
356
|
result: {
|
|
357
357
|
protocolVersion: "2024-11-05",
|
|
358
358
|
capabilities: { tools: {} },
|
|
@@ -362,12 +362,12 @@ process.stdin.on("data", (chunk: Buffer) => {
|
|
|
362
362
|
} else if (method === "tools/list") {
|
|
363
363
|
respond({
|
|
364
364
|
jsonrpc: "2.0",
|
|
365
|
-
id,
|
|
365
|
+
id: requestId,
|
|
366
366
|
result: {
|
|
367
|
-
tools: tools.map((
|
|
368
|
-
name:
|
|
369
|
-
description:
|
|
370
|
-
inputSchema:
|
|
367
|
+
tools: tools.map((toolDef) => ({
|
|
368
|
+
name: toolDef.name,
|
|
369
|
+
description: toolDef.description,
|
|
370
|
+
inputSchema: toolDef.inputSchema,
|
|
371
371
|
})),
|
|
372
372
|
},
|
|
373
373
|
});
|
|
@@ -375,7 +375,7 @@ process.stdin.on("data", (chunk: Buffer) => {
|
|
|
375
375
|
if (!params?.name) {
|
|
376
376
|
respond({
|
|
377
377
|
jsonrpc: "2.0",
|
|
378
|
-
id,
|
|
378
|
+
id: requestId,
|
|
379
379
|
error: {
|
|
380
380
|
code: -32602,
|
|
381
381
|
message: "Invalid params: tools/call requires params.name",
|
|
@@ -388,14 +388,14 @@ process.stdin.on("data", (chunk: Buffer) => {
|
|
|
388
388
|
.then((text) => {
|
|
389
389
|
respond({
|
|
390
390
|
jsonrpc: "2.0",
|
|
391
|
-
id,
|
|
391
|
+
id: requestId,
|
|
392
392
|
result: { content: [{ type: "text", text }] },
|
|
393
393
|
});
|
|
394
394
|
})
|
|
395
395
|
.catch((err: unknown) => {
|
|
396
396
|
respond({
|
|
397
397
|
jsonrpc: "2.0",
|
|
398
|
-
id,
|
|
398
|
+
id: requestId,
|
|
399
399
|
result: {
|
|
400
400
|
content: [{ type: "text", text: String(err) }],
|
|
401
401
|
isError: true,
|
|
@@ -403,7 +403,7 @@ process.stdin.on("data", (chunk: Buffer) => {
|
|
|
403
403
|
});
|
|
404
404
|
});
|
|
405
405
|
} else if (method === "ping") {
|
|
406
|
-
respond({ jsonrpc: "2.0", id, result: {} });
|
|
406
|
+
respond({ jsonrpc: "2.0", id: requestId, result: {} });
|
|
407
407
|
}
|
|
408
408
|
// notifications/initialized and other notifications: no response needed
|
|
409
409
|
}
|
|
@@ -17,7 +17,7 @@ export interface McpTool {
|
|
|
17
17
|
|
|
18
18
|
export const mcpTools: McpTool[] = [readXPost, searchX];
|
|
19
19
|
|
|
20
|
-
const toolMap = new Map(mcpTools.map((
|
|
20
|
+
const toolMap = new Map(mcpTools.map((tool) => [tool.definition.name, tool]));
|
|
21
21
|
|
|
22
22
|
export function isMcpToolEnabled(tool: McpTool): boolean {
|
|
23
23
|
return (tool.requiredEnv ?? []).every((key) => !!process.env[key]);
|
|
@@ -34,11 +34,11 @@ interface McpToolParams {
|
|
|
34
34
|
// GET /api/mcp-tools — returns { name, enabled, requiredEnv } for each tool (used by the role builder UI)
|
|
35
35
|
mcpToolsRouter.get(API_ROUTES.mcpTools.list, (_req: Request, res: Response) => {
|
|
36
36
|
res.json(
|
|
37
|
-
mcpTools.map((
|
|
38
|
-
name:
|
|
39
|
-
enabled: isMcpToolEnabled(
|
|
40
|
-
requiredEnv:
|
|
41
|
-
prompt:
|
|
37
|
+
mcpTools.map((tool) => ({
|
|
38
|
+
name: tool.definition.name,
|
|
39
|
+
enabled: isMcpToolEnabled(tool),
|
|
40
|
+
requiredEnv: tool.requiredEnv ?? [],
|
|
41
|
+
prompt: tool.prompt,
|
|
42
42
|
})),
|
|
43
43
|
);
|
|
44
44
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { errorMessage } from "../../utils/errors.js";
|
|
2
2
|
import { safeResponseText } from "../../utils/http.js";
|
|
3
|
+
import { toUtcIsoDate } from "../../utils/date.js";
|
|
3
4
|
import { env } from "../../system/env.js";
|
|
4
5
|
|
|
5
6
|
const X_API_BASE = "https://api.twitter.com/2";
|
|
@@ -56,7 +57,7 @@ async function fetchX(path: string): Promise<XApiResponse> {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function formatTweet(tweet: XTweet, author?: XUser, url?: string): string {
|
|
59
|
-
const date = tweet.created_at ? new Date(tweet.created_at)
|
|
60
|
+
const date = tweet.created_at ? toUtcIsoDate(new Date(tweet.created_at)) : "";
|
|
60
61
|
const dateSuffix = date ? ` · ${date}` : "";
|
|
61
62
|
const byline = author ? `@${author.username} (${author.name})${dateSuffix}` : date;
|
|
62
63
|
const metrics = tweet.public_metrics
|
|
@@ -64,7 +65,7 @@ function formatTweet(tweet: XTweet, author?: XUser, url?: string): string {
|
|
|
64
65
|
: "";
|
|
65
66
|
const link = url ?? "";
|
|
66
67
|
return [byline, "", tweet.text, "", metrics, link]
|
|
67
|
-
.filter((
|
|
68
|
+
.filter((line) => line !== undefined)
|
|
68
69
|
.join("\n")
|
|
69
70
|
.trimEnd();
|
|
70
71
|
}
|
|
@@ -104,12 +105,12 @@ export const readXPost = {
|
|
|
104
105
|
return errorMessage(err);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
if (data.errors?.length) return `X API error: ${data.errors.map((
|
|
108
|
+
if (data.errors?.length) return `X API error: ${data.errors.map((err) => err.detail).join("; ")}`;
|
|
108
109
|
|
|
109
110
|
const tweet = data.data as XTweet | undefined;
|
|
110
111
|
if (!tweet) return "Tweet not found.";
|
|
111
112
|
|
|
112
|
-
const author = data.includes?.users?.find((
|
|
113
|
+
const author = data.includes?.users?.find((user) => user.id === tweet.author_id);
|
|
113
114
|
const canonicalUrl = author ? `https://x.com/${author.username}/status/${tweet.id}` : undefined;
|
|
114
115
|
return formatTweet(tweet, author, canonicalUrl);
|
|
115
116
|
},
|
|
@@ -168,13 +169,13 @@ export const searchX = {
|
|
|
168
169
|
return errorMessage(err);
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
if (data.errors?.length) return `X API error: ${data.errors.map((
|
|
172
|
+
if (data.errors?.length) return `X API error: ${data.errors.map((err) => err.detail).join("; ")}`;
|
|
172
173
|
|
|
173
174
|
const tweets = Array.isArray(data.data) ? data.data : [];
|
|
174
175
|
if (tweets.length === 0) return `No recent posts found for: "${query}"`;
|
|
175
176
|
|
|
176
177
|
const users = data.includes?.users ?? [];
|
|
177
|
-
const userMap = new Map(users.map((
|
|
178
|
+
const userMap = new Map(users.map((user) => [user.id, user]));
|
|
178
179
|
|
|
179
180
|
const lines: string[] = [`Search: "${query}" — ${tweets.length} result${tweets.length !== 1 ? "s" : ""}`, ""];
|
|
180
181
|
tweets.forEach((tweet, i) => {
|