mulmoclaude 0.1.2 → 0.3.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 +1 -1
- package/client/assets/{index-KNLBjwuh.css → index-Bm70FDU2.css} +1 -1
- package/client/assets/{index-D8rhwXLq.js → index-eHWB79u5.js} +3 -3
- package/client/index.html +2 -2
- package/package.json +1 -1
- package/server/agent/config.ts +12 -12
- package/server/agent/mcp-server.ts +19 -19
- package/server/agent/mcp-tools/x.ts +5 -5
- package/server/agent/prompt.ts +9 -4
- package/server/agent/sandboxMounts.ts +7 -7
- package/server/agent/stream.ts +4 -4
- package/server/api/routes/files.ts +9 -9
- package/server/api/routes/scheduler.ts +8 -8
- package/server/api/routes/schedulerHandlers.ts +12 -12
- package/server/api/routes/schedulerTasks.ts +14 -14
- package/server/api/routes/sessions.ts +24 -24
- package/server/api/routes/todosColumnsHandlers.ts +30 -30
- package/server/api/routes/wiki.ts +14 -14
- 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 +19 -19
- package/server/utils/date.ts +18 -18
- package/server/utils/files/atomic.ts +9 -9
- package/server/utils/files/html-io.ts +5 -5
- package/server/utils/files/image-store.ts +2 -2
- package/server/utils/files/journal-io.ts +2 -2
- package/server/utils/files/naming.ts +2 -2
- package/server/utils/files/roles-io.ts +10 -10
- package/server/utils/files/scheduler-io.ts +5 -5
- package/server/utils/files/session-io.ts +35 -35
- package/server/utils/files/spreadsheet-store.ts +2 -2
- package/server/utils/files/todos-io.ts +9 -9
- package/server/utils/files/user-tasks-io.ts +5 -5
- package/server/workspace/chat-index/indexer.ts +15 -15
- package/server/workspace/custom-dirs.ts +11 -11
- package/server/workspace/journal/archivist.ts +35 -35
- package/server/workspace/journal/dailyPass.ts +31 -28
- package/server/workspace/journal/indexFile.ts +29 -25
- package/server/workspace/reference-dirs.ts +18 -18
- package/server/workspace/roles.ts +6 -6
- package/server/workspace/skills/discovery.ts +4 -4
- package/server/workspace/skills/user-tasks.ts +34 -34
- package/server/workspace/sources/arxivDiscovery.ts +8 -8
- 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/interests.ts +9 -9
- package/server/workspace/sources/pipeline/index.ts +6 -6
- package/server/workspace/sources/pipeline/plan.ts +5 -5
- package/server/workspace/sources/registry.ts +16 -16
- package/server/workspace/sources/robots.ts +14 -14
- package/server/workspace/sources/sourceState.ts +11 -9
- 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/src/App.vue +30 -30
- package/src/components/ChatInput.vue +7 -7
- package/src/components/LockStatusPopup.vue +2 -2
- package/src/components/NotificationToast.vue +2 -2
- package/src/components/RoleSelector.vue +2 -2
- package/src/components/SessionHistoryPanel.vue +6 -6
- package/src/components/SettingsMcpTab.vue +7 -7
- package/src/components/SettingsModal.vue +3 -3
- package/src/components/SettingsReferenceDirsTab.vue +10 -10
- package/src/components/SettingsWorkspaceDirsTab.vue +5 -5
- package/src/components/SuggestionsPanel.vue +2 -2
- package/src/components/todo/TodoAddDialog.vue +2 -2
- package/src/components/todo/TodoEditPanel.vue +2 -2
- package/src/components/todo/TodoListView.vue +5 -5
- package/src/composables/useCanvasViewMode.ts +5 -5
- package/src/composables/useClickOutside.ts +2 -2
- package/src/composables/useFreshPluginData.ts +3 -3
- package/src/composables/useKeyNavigation.ts +11 -11
- 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/useSessionSync.ts +8 -8
- package/src/composables/useViewLayout.ts +2 -2
- package/src/config/roles.ts +2 -2
- package/src/plugins/chart/Preview.vue +4 -4
- package/src/plugins/manageSkills/View.vue +3 -3
- package/src/plugins/manageSource/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +2 -2
- package/src/plugins/presentHtml/helpers.ts +8 -8
- package/src/plugins/presentMulmoScript/View.vue +4 -4
- package/src/plugins/presentMulmoScript/helpers.ts +1 -1
- package/src/plugins/scheduler/Preview.vue +6 -6
- package/src/plugins/scheduler/TasksTab.vue +4 -4
- package/src/plugins/textResponse/View.vue +2 -2
- package/src/plugins/todo/Preview.vue +2 -2
- package/src/plugins/todo/View.vue +11 -11
- package/src/plugins/todo/composables/useTodos.ts +5 -5
- package/src/plugins/wiki/Preview.vue +5 -5
- package/src/plugins/wiki/helpers.ts +4 -4
- package/src/router/guards.ts +12 -12
- package/src/types/session.ts +4 -3
- package/src/utils/agent/request.ts +3 -3
- 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/rewriteMarkdownImageRefs.ts +5 -5
- package/src/utils/markdown/extractFirstH1.ts +2 -2
- package/src/utils/path/relativeLink.ts +15 -15
- 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
|
@@ -66,15 +66,15 @@ function parseValue(raw: string): FrontmatterValue {
|
|
|
66
66
|
if (arrayMatch) {
|
|
67
67
|
return arrayMatch[1]
|
|
68
68
|
.split(",")
|
|
69
|
-
.map((
|
|
70
|
-
.filter((
|
|
69
|
+
.map((item) => unquote(item.trim()))
|
|
70
|
+
.filter((item) => item.length > 0);
|
|
71
71
|
}
|
|
72
72
|
return unquote(raw);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function unquote(
|
|
76
|
-
if ((
|
|
77
|
-
return
|
|
75
|
+
function unquote(str: string): string {
|
|
76
|
+
if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
|
|
77
|
+
return str.slice(1, -1);
|
|
78
78
|
}
|
|
79
|
-
return
|
|
79
|
+
return str;
|
|
80
80
|
}
|
|
@@ -51,7 +51,7 @@ function shouldSkip(url: string): boolean {
|
|
|
51
51
|
function resolveWorkspacePath(basePath: string, url: string): string | null {
|
|
52
52
|
// Absolute-within-workspace (e.g. "/images/foo.png") — reset base.
|
|
53
53
|
const isAbsolute = url.startsWith("/");
|
|
54
|
-
const baseSegs = isAbsolute ? [] : basePath.split("/").filter((
|
|
54
|
+
const baseSegs = isAbsolute ? [] : basePath.split("/").filter((seg) => seg !== "" && seg !== ".");
|
|
55
55
|
const segs = [...baseSegs];
|
|
56
56
|
|
|
57
57
|
const urlSegs = (isAbsolute ? url.slice(1) : url).split("/");
|
|
@@ -76,9 +76,9 @@ function extractBracketedAlt(raw: string): string | null {
|
|
|
76
76
|
if (!raw.startsWith("![")) return null;
|
|
77
77
|
let depth = 1;
|
|
78
78
|
for (let i = 2; i < raw.length; i++) {
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
else if (
|
|
79
|
+
const char = raw[i];
|
|
80
|
+
if (char === "[") depth++;
|
|
81
|
+
else if (char === "]") {
|
|
82
82
|
depth--;
|
|
83
83
|
if (depth === 0) return raw.slice(2, i);
|
|
84
84
|
}
|
|
@@ -123,7 +123,7 @@ function getContainerChildren(token: Token): Token[] | null {
|
|
|
123
123
|
// Returns true if the container was rendered via its children, false
|
|
124
124
|
// if the caller should fall back to emitting the parent's raw.
|
|
125
125
|
function renderContainerChildren(raw: string, children: Token[], basePath: string, out: string[]): boolean {
|
|
126
|
-
const joined = children.map((
|
|
126
|
+
const joined = children.map((token) => (token as { raw?: string }).raw ?? "").join("");
|
|
127
127
|
if (joined === "") return false;
|
|
128
128
|
const idx = raw.indexOf(joined);
|
|
129
129
|
if (idx < 0) return false;
|
|
@@ -30,8 +30,8 @@ export function extractFirstH1(markdown: string): string | null {
|
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function splitLines(
|
|
34
|
-
return
|
|
33
|
+
function splitLines(str: string): string[] {
|
|
34
|
+
return str.split(/\r\n|\r|\n/);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function isInlineSpace(code: number): boolean {
|
|
@@ -73,13 +73,13 @@ export function resolveWorkspaceLink(currentFilePath: string, href: string): str
|
|
|
73
73
|
|
|
74
74
|
// Drop any trailing #fragment or ?query from a path-like string.
|
|
75
75
|
// Whichever marker comes first wins.
|
|
76
|
-
function stripFragmentAndQuery(
|
|
77
|
-
const hashIdx =
|
|
78
|
-
const queryIdx =
|
|
79
|
-
let end =
|
|
76
|
+
function stripFragmentAndQuery(str: string): string {
|
|
77
|
+
const hashIdx = str.indexOf("#");
|
|
78
|
+
const queryIdx = str.indexOf("?");
|
|
79
|
+
let end = str.length;
|
|
80
80
|
if (hashIdx !== -1 && hashIdx < end) end = hashIdx;
|
|
81
81
|
if (queryIdx !== -1 && queryIdx < end) end = queryIdx;
|
|
82
|
-
return
|
|
82
|
+
return str.slice(0, end);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// If `resolvedPath` points at a chat session log (e.g.
|
|
@@ -95,26 +95,26 @@ export function extractSessionIdFromPath(resolvedPath: string): string | null {
|
|
|
95
95
|
const JSONL_SUFFIX = ".jsonl";
|
|
96
96
|
if (!resolvedPath.startsWith(CHAT_PREFIX)) return null;
|
|
97
97
|
if (!resolvedPath.endsWith(JSONL_SUFFIX)) return null;
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
if (
|
|
101
|
-
return
|
|
98
|
+
const sessionId = resolvedPath.slice(CHAT_PREFIX.length, resolvedPath.length - JSONL_SUFFIX.length);
|
|
99
|
+
if (sessionId.length === 0) return null;
|
|
100
|
+
if (sessionId.includes("/")) return null;
|
|
101
|
+
return sessionId;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// POSIX-style dirname. The file viewer always uses "/" separators
|
|
105
105
|
// so we don't need to worry about Windows paths.
|
|
106
|
-
function posixDirname(
|
|
107
|
-
const i =
|
|
108
|
-
return i === -1 ? "" :
|
|
106
|
+
function posixDirname(path: string): string {
|
|
107
|
+
const i = path.lastIndexOf("/");
|
|
108
|
+
return i === -1 ? "" : path.slice(0, i);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Collapse "./" and "../" in a workspace path. Rejects paths that
|
|
112
112
|
// escape above the workspace root. Returns null for the empty-path
|
|
113
113
|
// case so the caller can bail out. Callers are expected to strip
|
|
114
114
|
// #fragment / ?query before invoking this function.
|
|
115
|
-
function normalizeWorkspacePath(
|
|
116
|
-
if (
|
|
117
|
-
const parts =
|
|
115
|
+
function normalizeWorkspacePath(path: string): string | null {
|
|
116
|
+
if (path.length === 0) return null;
|
|
117
|
+
const parts = path.split("/");
|
|
118
118
|
const stack: string[] = [];
|
|
119
119
|
for (const part of parts) {
|
|
120
120
|
if (part === "" || part === ".") continue;
|
package/src/utils/role/icon.ts
CHANGED
|
@@ -11,10 +11,10 @@ import type { Role } from "../../config/roles";
|
|
|
11
11
|
const MATERIAL_ICON_RE = /^[a-z_]+$/;
|
|
12
12
|
|
|
13
13
|
export function roleIcon(roles: Role[], roleId: string): string {
|
|
14
|
-
const icon = roles.find((
|
|
14
|
+
const icon = roles.find((role) => role.id === roleId)?.icon ?? "star";
|
|
15
15
|
return MATERIAL_ICON_RE.test(icon) ? icon : "smart_toy";
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function roleName(roles: Role[], roleId: string): string {
|
|
19
|
-
return roles.find((
|
|
19
|
+
return roles.find((role) => role.id === roleId)?.name ?? roleId;
|
|
20
20
|
}
|
package/src/utils/role/merge.ts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
import type { Role } from "../../config/roles";
|
|
6
6
|
|
|
7
7
|
export function mergeRoles(builtin: Role[], custom: Role[]): Role[] {
|
|
8
|
-
const customIds = new Set(custom.map((
|
|
9
|
-
return [...builtin.filter((
|
|
8
|
+
const customIds = new Set(custom.map((role) => role.id));
|
|
9
|
+
return [...builtin.filter((role) => !customIds.has(role.id)), ...custom];
|
|
10
10
|
}
|
|
@@ -8,5 +8,5 @@ const GEMINI_PLUGINS = new Set<ToolName>([TOOL_NAMES.generateImage, TOOL_NAMES.p
|
|
|
8
8
|
|
|
9
9
|
/** Whether the given role uses any plugin that requires a Gemini API key. */
|
|
10
10
|
export function needsGemini(roles: Role[], roleId: string): boolean {
|
|
11
|
-
return (roles.find((
|
|
11
|
+
return (roles.find((role) => role.id === roleId)?.availablePlugins ?? []).some((plugin) => GEMINI_PLUGINS.has(plugin));
|
|
12
12
|
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ActiveSession } from "../../types/session";
|
|
4
4
|
|
|
5
|
-
export function createEmptySession(
|
|
5
|
+
export function createEmptySession(sessionId: string, roleId: string): ActiveSession {
|
|
6
6
|
const now = new Date().toISOString();
|
|
7
7
|
return {
|
|
8
|
-
id,
|
|
8
|
+
id: sessionId,
|
|
9
9
|
roleId,
|
|
10
10
|
toolResults: [],
|
|
11
11
|
resultTimestamps: new Map(),
|
|
@@ -79,7 +79,7 @@ export function applyTextEvent(session: ActiveSession, message: string, source:
|
|
|
79
79
|
/** In-place update a result that was re-emitted by a plugin view
|
|
80
80
|
* (e.g. after the user edits a chart config). */
|
|
81
81
|
export function updateResult(session: ActiveSession, updatedResult: ToolResultComplete): void {
|
|
82
|
-
const index = session.toolResults.findIndex((
|
|
82
|
+
const index = session.toolResults.findIndex((result) => result.uuid === updatedResult.uuid);
|
|
83
83
|
if (index !== -1) {
|
|
84
84
|
Object.assign(session.toolResults[index], updatedResult);
|
|
85
85
|
}
|
|
@@ -89,7 +89,7 @@ export function updateResult(session: ActiveSession, updatedResult: ToolResultCo
|
|
|
89
89
|
* result list. Selects the result only on insert; in-place updates
|
|
90
90
|
* preserve the user's current selection. */
|
|
91
91
|
export function applyToolResultToSession(session: ActiveSession, result: ToolResultComplete): void {
|
|
92
|
-
const idx = session.toolResults.findIndex((
|
|
92
|
+
const idx = session.toolResults.findIndex((existing) => existing.uuid === result.uuid);
|
|
93
93
|
if (idx >= 0) {
|
|
94
94
|
session.toolResults[idx] = result;
|
|
95
95
|
} else {
|
package/src/utils/tools/dedup.ts
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
8
8
|
|
|
9
9
|
export function deduplicateResults(all: ToolResultComplete[]): ToolResultComplete[] {
|
|
10
|
-
return all.filter((
|
|
11
|
-
if (
|
|
10
|
+
return all.filter((result, i) => {
|
|
11
|
+
if (result.toolName === "text-response") return true;
|
|
12
12
|
const next = all[i + 1];
|
|
13
13
|
if (!next) return true;
|
|
14
|
-
if (next.toolName !==
|
|
15
|
-
return !(
|
|
14
|
+
if (next.toolName !== result.toolName) return true;
|
|
15
|
+
return !(result.updating === true && next.updating === true);
|
|
16
16
|
});
|
|
17
17
|
}
|
|
@@ -9,9 +9,9 @@ import { isRecord } from "../types";
|
|
|
9
9
|
// Type guard: a text-response entry whose `data.role` is `"user"`.
|
|
10
10
|
// Used by App.vue to find the first user message in a live session
|
|
11
11
|
// when building the merged history list.
|
|
12
|
-
export function isUserTextResponse(
|
|
13
|
-
if (
|
|
14
|
-
const data =
|
|
12
|
+
export function isUserTextResponse(res: ToolResultComplete): boolean {
|
|
13
|
+
if (res.toolName !== "text-response") return false;
|
|
14
|
+
const data = res.data;
|
|
15
15
|
if (!isRecord(data)) return false;
|
|
16
16
|
return data.role === "user";
|
|
17
17
|
}
|
package/src/utils/types.ts
CHANGED
|
@@ -20,12 +20,12 @@ export function isNonEmptyString(value: unknown): value is string {
|
|
|
20
20
|
/** Record whose values are all strings. */
|
|
21
21
|
export function isStringRecord(value: unknown): value is Record<string, string> {
|
|
22
22
|
if (!isRecord(value)) return false;
|
|
23
|
-
return Object.values(value).every((
|
|
23
|
+
return Object.values(value).every((val) => typeof val === "string");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** String array (every element is a string). */
|
|
27
27
|
export function isStringArray(value: unknown): value is string[] {
|
|
28
|
-
return Array.isArray(value) && value.every((
|
|
28
|
+
return Array.isArray(value) && value.every((val) => typeof val === "string");
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Error-like object with a `code` property. */
|