mulmoclaude 0.3.0 → 0.5.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-DiKaqnKs.js +5 -0
- package/client/assets/{index-eHWB79u5.js → index-C94GcmNa.js} +203 -198
- package/client/assets/index-CY-WpQUm.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-5ipqh8Pe.js} +5 -5
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/client/index.html +2 -4
- package/package.json +17 -15
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/backend/claude-code.ts +170 -0
- package/server/agent/backend/index.ts +14 -0
- package/server/agent/backend/types.ts +65 -0
- package/server/agent/index.ts +31 -159
- package/server/agent/mcp-server.ts +88 -10
- package/server/agent/mcp-tools/index.ts +8 -7
- package/server/agent/mcp-tools/notify.ts +76 -0
- package/server/agent/mcp-tools/x.ts +12 -2
- package/server/agent/plugin-names.ts +10 -4
- package/server/agent/prompt.ts +187 -26
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +3 -3
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +99 -4
- package/server/api/routes/chart.ts +13 -0
- package/server/api/routes/chat-index.ts +2 -1
- package/server/api/routes/config.ts +35 -8
- package/server/api/routes/files.ts +75 -24
- package/server/api/routes/html.ts +15 -2
- package/server/api/routes/image.ts +75 -20
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/news.ts +146 -0
- package/server/api/routes/notifications.ts +58 -2
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +73 -91
- package/server/api/routes/presentHtml.ts +9 -0
- package/server/api/routes/roles.ts +12 -2
- package/server/api/routes/scheduler.ts +20 -11
- package/server/api/routes/schedulerTasks.ts +58 -21
- package/server/api/routes/sessions.ts +15 -4
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +26 -5
- package/server/api/routes/sources.ts +8 -7
- package/server/api/routes/todos.ts +30 -0
- package/server/api/routes/todosColumnsHandlers.ts +13 -27
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki/frontmatter.ts +86 -0
- package/server/api/routes/wiki.ts +335 -75
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +32 -8
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +26 -16
- package/server/events/resolveRelayBridgeOptions.ts +125 -0
- package/server/index.ts +72 -49
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +15 -5
- package/server/system/macosNotify.ts +152 -0
- package/server/utils/errors.ts +11 -2
- package/server/utils/fetch.ts +54 -0
- package/server/utils/files/atomic.ts +18 -17
- package/server/utils/files/image-store.ts +19 -13
- package/server/utils/files/journal-io.ts +2 -2
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-image-fill.ts +131 -0
- package/server/utils/files/markdown-store.ts +22 -6
- package/server/utils/files/naming.ts +20 -10
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +4 -4
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/spreadsheet-store.ts +15 -10
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +30 -4
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/id.ts +40 -8
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- package/server/utils/logPreview.ts +24 -0
- 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/promptMeta.ts +32 -0
- package/server/utils/request.ts +12 -6
- package/server/utils/slug.ts +65 -4
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/index.ts +1 -1
- package/server/workspace/chat-index/summarizer.ts +5 -5
- package/server/workspace/custom-dirs.ts +5 -5
- package/server/workspace/helps/gemini.md +57 -0
- package/server/workspace/helps/index.md +2 -1
- package/server/workspace/helps/sources.md +42 -0
- package/server/workspace/helps/wiki.md +40 -5
- package/server/workspace/journal/archivist-cli.ts +121 -0
- package/server/workspace/journal/{archivist.ts → archivist-schemas.ts} +12 -120
- package/server/workspace/journal/dailyPass.ts +78 -38
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +56 -5
- package/server/workspace/journal/memoryExtractor.ts +1 -1
- package/server/workspace/journal/optimizationPass.ts +4 -5
- package/server/workspace/journal/paths.ts +8 -24
- package/server/workspace/journal/state.ts +18 -8
- package/server/workspace/news/reader.ts +248 -0
- package/server/workspace/paths.ts +4 -3
- package/server/workspace/reference-dirs.ts +3 -3
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +5 -4
- package/server/workspace/skills/user-tasks.ts +3 -2
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +2 -2
- package/server/workspace/sources/classifier.ts +1 -1
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +3 -3
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +59 -13
- package/server/workspace/sources/pipeline/index.ts +59 -7
- package/server/workspace/sources/pipeline/notify.ts +13 -5
- package/server/workspace/sources/pipeline/plan.ts +11 -9
- package/server/workspace/sources/pipeline/summarize.ts +1 -1
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/sourceState.ts +9 -4
- 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/workspace.ts +7 -7
- package/src/App.vue +477 -251
- package/src/components/CanvasViewToggle.vue +12 -10
- package/src/components/ChatInput.vue +112 -105
- package/src/components/FileContentHeader.vue +10 -7
- package/src/components/FileContentRenderer.vue +37 -10
- package/src/components/FileTree.vue +34 -4
- package/src/components/FileTreePane.vue +32 -27
- package/src/components/FilesView.vue +5 -3
- package/src/components/FilterChip.vue +22 -0
- package/src/components/LockStatusPopup.vue +19 -13
- package/src/components/NewsView.vue +252 -0
- package/src/components/NotificationBell.vue +35 -9
- package/src/components/NotificationToast.vue +4 -1
- package/src/components/PageChatComposer.vue +101 -0
- package/src/components/PluginLauncher.vue +36 -62
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/RoleSelector.vue +3 -2
- package/src/components/SessionHeaderControls.vue +63 -0
- package/src/components/SessionHistoryExpandButton.vue +30 -0
- package/src/components/SessionHistoryPanel.vue +64 -93
- package/src/components/SessionHistoryToggleButton.vue +40 -0
- package/src/components/SessionRoleIcon.vue +72 -0
- package/src/components/SessionSidebar.vue +96 -0
- package/src/components/SessionTabBar.vue +44 -51
- package/src/components/SettingsMcpTab.vue +361 -52
- package/src/components/SettingsModal.vue +203 -72
- package/src/components/SettingsReferenceDirsTab.vue +72 -51
- package/src/components/SettingsWorkspaceDirsTab.vue +74 -51
- package/src/components/SidebarHeader.vue +50 -16
- package/src/components/SourcesManager.vue +900 -0
- package/src/components/SourcesView.vue +45 -0
- package/src/components/StackView.vue +84 -48
- package/src/components/SuggestionsPanel.vue +25 -36
- package/src/components/SystemFileBanner.vue +106 -0
- package/src/components/ThinkingIndicator.vue +41 -0
- package/src/components/TodoExplorer.vue +72 -22
- package/src/components/todo/TodoAddDialog.vue +17 -12
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +15 -10
- package/src/components/todo/TodoKanbanView.vue +16 -6
- package/src/components/todo/TodoListView.vue +14 -3
- package/src/components/todo/TodoTableView.vue +36 -5
- package/src/composables/favicon/conditions.ts +76 -0
- package/src/composables/favicon/resolveColor.ts +93 -0
- package/src/composables/favicon/types.ts +61 -0
- package/src/composables/useAppApi.ts +23 -0
- package/src/composables/useChatScroll.ts +5 -5
- package/src/composables/useCurrentRole.ts +32 -0
- package/src/composables/useDynamicFavicon.ts +174 -58
- package/src/composables/useEventListeners.ts +7 -12
- package/src/composables/useFaviconState.ts +93 -12
- package/src/composables/useFileSelection.ts +25 -6
- package/src/composables/useHealth.ts +76 -7
- package/src/composables/useLayoutMode.ts +27 -0
- package/src/composables/useNewsItems.ts +38 -0
- package/src/composables/useNewsReadState.ts +75 -0
- package/src/composables/useNotifications.ts +76 -13
- package/src/composables/usePendingCalls.ts +11 -1
- package/src/composables/useRoles.ts +6 -10
- package/src/composables/useRunElapsed.ts +80 -0
- package/src/composables/useSessionDerived.ts +21 -5
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useSidePanelVisible.ts +25 -0
- package/src/composables/useViewLayout.ts +16 -37
- package/src/config/apiRoutes.ts +19 -6
- package/src/config/historyFilters.ts +30 -0
- package/src/config/mcpCatalog.ts +285 -0
- package/src/config/mcpTypes.ts +26 -0
- package/src/config/roles.ts +19 -51
- package/src/config/systemFileDescriptors.ts +170 -0
- package/src/config/toolNames.ts +6 -1
- package/src/config/workspacePaths.ts +1 -0
- package/src/index.css +14 -0
- package/src/lang/de.ts +706 -0
- package/src/lang/en.ts +726 -0
- package/src/lang/es.ts +712 -0
- package/src/lang/fr.ts +704 -0
- package/src/lang/ja.ts +707 -0
- package/src/lang/ko.ts +709 -0
- package/src/lang/pt-BR.ts +702 -0
- package/src/lang/zh.ts +705 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +3 -0
- package/src/plugins/canvas/View.vue +104 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/canvas/index.ts +3 -2
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/chart/index.ts +3 -2
- package/src/plugins/editImage/index.ts +3 -2
- package/src/plugins/generateImage/index.ts +3 -2
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +67 -46
- package/src/plugins/manageRoles/index.ts +3 -2
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +39 -34
- package/src/plugins/manageSkills/index.ts +3 -2
- package/src/plugins/manageSource/Preview.vue +1 -1
- package/src/plugins/manageSource/View.vue +3 -687
- package/src/plugins/manageSource/index.ts +3 -2
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +164 -73
- package/src/plugins/markdown/definition.ts +6 -4
- package/src/plugins/markdown/index.ts +3 -2
- package/src/plugins/presentForm/Preview.vue +99 -0
- package/src/plugins/presentForm/View.vue +675 -0
- package/src/plugins/presentForm/definition.ts +127 -0
- package/src/plugins/presentForm/index.ts +18 -0
- package/src/plugins/presentForm/plugin.ts +94 -0
- package/src/plugins/presentForm/types.ts +109 -0
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentHtml/index.ts +3 -2
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +36 -26
- package/src/plugins/presentMulmoScript/index.ts +3 -2
- package/src/plugins/scheduler/AutomationsPreview.vue +37 -0
- package/src/plugins/scheduler/AutomationsView.vue +23 -0
- package/src/plugins/scheduler/CalendarView.vue +23 -0
- package/src/plugins/scheduler/LegacySchedulerView.vue +32 -0
- package/src/plugins/scheduler/Preview.vue +7 -4
- package/src/plugins/scheduler/TasksTab.vue +119 -28
- package/src/plugins/scheduler/View.vue +75 -32
- package/src/plugins/scheduler/automationsDefinition.ts +58 -0
- package/src/plugins/scheduler/calendarDefinition.ts +46 -0
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/scheduler/index.ts +68 -14
- package/src/plugins/scheduler/legacyShape.ts +34 -0
- package/src/plugins/spreadsheet/Preview.vue +9 -5
- package/src/plugins/spreadsheet/View.vue +43 -57
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +2 -1
- package/src/plugins/spreadsheet/index.ts +3 -2
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +42 -45
- package/src/plugins/textResponse/utils.ts +25 -0
- package/src/plugins/todo/Preview.vue +11 -6
- package/src/plugins/todo/View.vue +27 -13
- package/src/plugins/todo/composables/useTodos.ts +3 -1
- package/src/plugins/todo/index.ts +3 -2
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +5 -2
- package/src/plugins/wiki/View.vue +539 -92
- package/src/plugins/wiki/index.ts +5 -2
- package/src/plugins/wiki/route.ts +121 -0
- package/src/router/guards.ts +43 -24
- package/src/router/index.ts +53 -26
- package/src/router/pageRoutes.ts +23 -0
- package/src/tools/index.ts +12 -5
- package/src/tools/legacyPluginNames.ts +13 -0
- package/src/types/notification.ts +31 -6
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/eventDispatch.ts +3 -6
- package/src/utils/agent/formatElapsed.ts +37 -0
- package/src/utils/agent/request.ts +22 -1
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/canvas/sidePanelVisible.ts +19 -0
- package/src/utils/dom/scrollIntoViewByTestId.ts +38 -0
- package/src/utils/errors.ts +9 -2
- package/src/utils/files/filename.ts +24 -0
- package/src/utils/filesPreview/schedulerPreview.ts +9 -3
- package/src/utils/id.ts +18 -0
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/markdown/taskList.ts +175 -0
- package/src/utils/mcp/interpolateSpec.ts +97 -0
- package/src/utils/notification/dispatch.ts +51 -15
- package/src/utils/path/workspaceLinkRouter.ts +99 -0
- package/src/utils/session/mergeSessions.ts +5 -0
- package/src/utils/sources/filter.ts +69 -0
- 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-Bm70FDU2.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/server/workspace/journal/linkRewrite.ts +0 -4
- package/src/components/ToolResultsPanel.vue +0 -77
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/plugins/scheduler/definition.ts +0 -57
- package/src/utils/canvas/viewMode.ts +0 -46
- package/src/utils/role/plugins.ts +0 -12
- package/src/utils/session/seedRoleDefault.ts +0 -35
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
|
@@ -77,12 +77,12 @@ function parseTranscriptEntries(jsonlContent: string): TranscriptEntry[] {
|
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
79
|
if (!isRecord(entry)) continue;
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
if (
|
|
83
|
-
const message =
|
|
80
|
+
const record = entry;
|
|
81
|
+
if (record.type !== EVENT_TYPES.text) continue;
|
|
82
|
+
if (record.source !== "user" && record.source !== "assistant") continue;
|
|
83
|
+
const message = record.message;
|
|
84
84
|
if (typeof message !== "string" || message.length === 0) continue;
|
|
85
|
-
out.push({ source:
|
|
85
|
+
out.push({ source: record.source, text: message });
|
|
86
86
|
}
|
|
87
87
|
return out;
|
|
88
88
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// See docs/sandbox-credentials.md for the user-facing contract.
|
|
16
16
|
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
import
|
|
18
|
+
import { existsSync, statSync } from "node:fs";
|
|
19
19
|
import { execFileSync } from "node:child_process";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { log } from "../system/logger/index.js";
|
|
@@ -105,7 +105,7 @@ export function resolveMountNames(names: readonly string[], allowed: Record<stri
|
|
|
105
105
|
|
|
106
106
|
function hostPathExists(spec: SandboxMountSpec): boolean {
|
|
107
107
|
try {
|
|
108
|
-
const stat =
|
|
108
|
+
const stat = statSync(spec.hostPath);
|
|
109
109
|
return spec.kind === "dir" ? stat.isDirectory() : stat.isFile();
|
|
110
110
|
} catch {
|
|
111
111
|
return false;
|
|
@@ -181,7 +181,7 @@ export function sshAgentForwardArgs(
|
|
|
181
181
|
skippedReason: "SSH_AUTH_SOCK not set on host",
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
|
-
if (!
|
|
184
|
+
if (!existsSync(sshAuthSock)) {
|
|
185
185
|
return {
|
|
186
186
|
args: [],
|
|
187
187
|
skippedReason: `SSH_AUTH_SOCK=${sshAuthSock} not found on host`,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { timingSafeEqual } from "crypto";
|
|
2
2
|
|
|
3
|
-
function safeEqual(
|
|
4
|
-
if (
|
|
5
|
-
return timingSafeEqual(Buffer.from(
|
|
3
|
+
function safeEqual(left: string, right: string): boolean {
|
|
4
|
+
if (left.length !== right.length) return false;
|
|
5
|
+
return timingSafeEqual(Buffer.from(left), Buffer.from(right));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
// Bearer token middleware (#272). Reject any `/api/*` request whose
|
package/server/api/auth/token.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// setting it once on both sides survives restarts.
|
|
22
22
|
|
|
23
23
|
import { randomBytes } from "crypto";
|
|
24
|
-
import
|
|
24
|
+
import { promises } from "fs";
|
|
25
25
|
import { writeFileAtomic } from "../../utils/files/index.js";
|
|
26
26
|
import { log } from "../../system/logger/index.js";
|
|
27
27
|
import { isNonEmptyString } from "../../utils/types.js";
|
|
@@ -83,7 +83,7 @@ function resolveToken(override: string | undefined): string {
|
|
|
83
83
|
*/
|
|
84
84
|
export async function deleteTokenFile(tokenPath: string = WORKSPACE_PATHS.sessionToken): Promise<void> {
|
|
85
85
|
try {
|
|
86
|
-
await
|
|
86
|
+
await promises.unlink(tokenPath);
|
|
87
87
|
} catch {
|
|
88
88
|
/* already gone — nothing to do */
|
|
89
89
|
}
|
|
@@ -27,7 +27,18 @@ import { logBackgroundError } from "../../utils/logBackgroundError.js";
|
|
|
27
27
|
import { createArgsCache, recordToolEvent } from "../../workspace/tool-trace/index.js";
|
|
28
28
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
29
29
|
import { EVENT_TYPES } from "../../../src/types/events.js";
|
|
30
|
-
import { isSessionOrigin } from "../../../src/types/session.js";
|
|
30
|
+
import { isSessionOrigin, type SessionOrigin } from "../../../src/types/session.js";
|
|
31
|
+
// Imports kept commented (instead of deleted) alongside the
|
|
32
|
+
// publishNotification call below — see the duplicate-notification
|
|
33
|
+
// comment near `endRun()` in `runAgentInBackground` for context.
|
|
34
|
+
// `SESSION_ORIGINS` is dragged into this same commented block
|
|
35
|
+
// because every remaining live reference to it lived inside the
|
|
36
|
+
// commented helper / call site; once those went, leaving the value
|
|
37
|
+
// import un-commented would trip the unused-import lint rule.
|
|
38
|
+
// (by snakajima)
|
|
39
|
+
// import { SESSION_ORIGINS } from "../../../src/types/session.js";
|
|
40
|
+
// import { NOTIFICATION_KINDS } from "../../../src/types/notification.js";
|
|
41
|
+
// import { publishNotification } from "../../events/notifications.js";
|
|
31
42
|
import { env } from "../../system/env.js";
|
|
32
43
|
import type { Attachment } from "@mulmobridge/protocol";
|
|
33
44
|
import { parseDataUrl } from "@mulmobridge/client";
|
|
@@ -105,12 +116,23 @@ export interface StartChatParams {
|
|
|
105
116
|
/** Where this session originates (#486). Accepts string for
|
|
106
117
|
* cross-package compatibility (chat-service passes string). */
|
|
107
118
|
origin?: string;
|
|
119
|
+
/** IANA timezone the user's browser resolved (e.g. "Asia/Tokyo").
|
|
120
|
+
* Validated server-side before it reaches the system prompt — an
|
|
121
|
+
* invalid or missing value falls back to server-local time. */
|
|
122
|
+
userTimezone?: string;
|
|
123
|
+
/** Flat primitive bag forwarded from the bridge handshake, string
|
|
124
|
+
* / number / boolean values only (see plans/feat-bridge-options-
|
|
125
|
+
* passthrough.md). The session-level `defaultRole` override is
|
|
126
|
+
* already applied upstream in chat-service; MulmoClaude doesn't
|
|
127
|
+
* read any other keys today. Accepted here so the typing matches
|
|
128
|
+
* `StartChatFn` exported by chat-service. */
|
|
129
|
+
bridgeOptions?: Readonly<Record<string, string | number | boolean>>;
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
export type StartChatResult = { kind: "started"; chatSessionId: string } | { kind: "error"; error: string; status?: number };
|
|
111
133
|
|
|
112
134
|
export async function startChat(params: StartChatParams): Promise<StartChatResult> {
|
|
113
|
-
const { message, roleId, chatSessionId, selectedImageData, attachments } = params;
|
|
135
|
+
const { message, roleId, chatSessionId, selectedImageData, attachments, userTimezone } = params;
|
|
114
136
|
|
|
115
137
|
if (!message || !roleId || !chatSessionId) {
|
|
116
138
|
return {
|
|
@@ -203,6 +225,8 @@ export async function startChat(params: StartChatParams): Promise<StartChatResul
|
|
|
203
225
|
requestStartedAt,
|
|
204
226
|
toolArgsCache: createArgsCache(),
|
|
205
227
|
attachments: mergeAttachments(selectedImageData, attachments),
|
|
228
|
+
userTimezone,
|
|
229
|
+
origin: validOrigin,
|
|
206
230
|
});
|
|
207
231
|
|
|
208
232
|
return { kind: "started", chatSessionId };
|
|
@@ -239,6 +263,7 @@ interface AgentBody {
|
|
|
239
263
|
roleId: string;
|
|
240
264
|
chatSessionId: string;
|
|
241
265
|
selectedImageData?: string;
|
|
266
|
+
userTimezone?: string;
|
|
242
267
|
}
|
|
243
268
|
|
|
244
269
|
interface ErrorResponse {
|
|
@@ -271,6 +296,12 @@ interface BackgroundRunParams {
|
|
|
271
296
|
requestStartedAt: number;
|
|
272
297
|
toolArgsCache: ReturnType<typeof createArgsCache>;
|
|
273
298
|
attachments: Attachment[] | undefined;
|
|
299
|
+
userTimezone: string | undefined;
|
|
300
|
+
// Where this run was triggered from. Used to decide whether to
|
|
301
|
+
// fire a completion notification: human-initiated runs don't (the
|
|
302
|
+
// user is right there in the UI), but scheduler / bridge / skill
|
|
303
|
+
// runs do (the user is probably away from the keyboard).
|
|
304
|
+
origin: SessionOrigin | undefined;
|
|
274
305
|
}
|
|
275
306
|
|
|
276
307
|
// Per-event side-effect context passed to `handleAgentEvent`.
|
|
@@ -357,8 +388,30 @@ async function flushTextAccumulator(ctx: EventContext): Promise<void> {
|
|
|
357
388
|
);
|
|
358
389
|
}
|
|
359
390
|
|
|
391
|
+
// Helper kept commented (instead of deleted) alongside the
|
|
392
|
+
// publishNotification call below — see the duplicate-notification
|
|
393
|
+
// comment near `endRun()` in `runAgentInBackground` for context.
|
|
394
|
+
// (by snakajima)
|
|
395
|
+
//
|
|
396
|
+
// // Build the title used for the agent-completion notification on
|
|
397
|
+
// // non-human runs. Surfaces both the role name and the trigger so
|
|
398
|
+
// // the user can read it in passing on a phone lock screen.
|
|
399
|
+
// function completionNotificationTitle(roleName: string, origin: SessionOrigin): string {
|
|
400
|
+
// switch (origin) {
|
|
401
|
+
// case SESSION_ORIGINS.scheduler:
|
|
402
|
+
// return `✅ ${roleName} (scheduler) finished`;
|
|
403
|
+
// case SESSION_ORIGINS.skill:
|
|
404
|
+
// return `✅ ${roleName} (skill) finished`;
|
|
405
|
+
// case SESSION_ORIGINS.bridge:
|
|
406
|
+
// return `✅ ${roleName} reply ready`;
|
|
407
|
+
// default:
|
|
408
|
+
// return `✅ ${roleName} finished`;
|
|
409
|
+
// }
|
|
410
|
+
// }
|
|
411
|
+
|
|
360
412
|
async function runAgentInBackground(params: BackgroundRunParams): Promise<void> {
|
|
361
|
-
const { decoratedMessage, role, chatSessionId, claudeSessionId, abortSignal, resultsFilePath, requestStartedAt, toolArgsCache, attachments } =
|
|
413
|
+
const { decoratedMessage, role, chatSessionId, claudeSessionId, abortSignal, resultsFilePath, requestStartedAt, toolArgsCache, attachments, userTimezone } =
|
|
414
|
+
params;
|
|
362
415
|
|
|
363
416
|
const eventCtx: EventContext = {
|
|
364
417
|
chatSessionId,
|
|
@@ -378,7 +431,17 @@ async function runAgentInBackground(params: BackgroundRunParams): Promise<void>
|
|
|
378
431
|
try {
|
|
379
432
|
while (true) {
|
|
380
433
|
let staleSessionDetected = false;
|
|
381
|
-
for await (const event of runAgent(
|
|
434
|
+
for await (const event of runAgent(
|
|
435
|
+
currentMessage,
|
|
436
|
+
role,
|
|
437
|
+
workspacePath,
|
|
438
|
+
chatSessionId,
|
|
439
|
+
PORT,
|
|
440
|
+
currentClaudeSessionId,
|
|
441
|
+
abortSignal,
|
|
442
|
+
attachments,
|
|
443
|
+
userTimezone,
|
|
444
|
+
)) {
|
|
382
445
|
if (failoverAttemptsRemaining > 0 && event.type === EVENT_TYPES.error && typeof event.message === "string" && isStaleSessionError(event.message)) {
|
|
383
446
|
// Swallow the error — we're about to recover. `break`
|
|
384
447
|
// abandons the current generator; since the event is only
|
|
@@ -431,6 +494,38 @@ async function runAgentInBackground(params: BackgroundRunParams): Promise<void>
|
|
|
431
494
|
});
|
|
432
495
|
} finally {
|
|
433
496
|
endRun(chatSessionId);
|
|
497
|
+
// Commented out: this would create a duplicate notification.
|
|
498
|
+
//
|
|
499
|
+
// `endRun(chatSessionId)` above flips `session.hasUnread = true`
|
|
500
|
+
// for every chat-session turn completion regardless of origin,
|
|
501
|
+
// which already lights up the red unread-count badge on the
|
|
502
|
+
// Session History Panel toggle button (driven by `hasUnread` →
|
|
503
|
+
// `useSessionDerived.unreadCount` →
|
|
504
|
+
// `SessionHistoryToggleButton.vue`). Firing
|
|
505
|
+
// `publishNotification` here adds a *second* red badge — on the
|
|
506
|
+
// notification bell — for the exact same event, in the same
|
|
507
|
+
// chrome row. Two indicators, one event = noise.
|
|
508
|
+
//
|
|
509
|
+
// The duplicate occurs whenever a chat session receives a new
|
|
510
|
+
// message, which is exactly what every code path through this
|
|
511
|
+
// `finally` represents. The initiator of the turn (human, bridge
|
|
512
|
+
// user, scheduled job, skill chain, another agent) does not
|
|
513
|
+
// change this — both badges flip together.
|
|
514
|
+
//
|
|
515
|
+
// Other `publishNotification` call sites (news pipeline, `notify`
|
|
516
|
+
// MCP tool, scheduled-test endpoint) do not post a chat-session
|
|
517
|
+
// message at the same time, so they are not duplicates and
|
|
518
|
+
// remain enabled.
|
|
519
|
+
//
|
|
520
|
+
// (by snakajima)
|
|
521
|
+
//
|
|
522
|
+
// if (params.origin && params.origin !== SESSION_ORIGINS.human) {
|
|
523
|
+
// publishNotification({
|
|
524
|
+
// kind: NOTIFICATION_KINDS.agent,
|
|
525
|
+
// title: completionNotificationTitle(params.role.name, params.origin),
|
|
526
|
+
// sessionId: chatSessionId,
|
|
527
|
+
// });
|
|
528
|
+
// }
|
|
434
529
|
// Fire-and-forget: journal + chat-index post-processing
|
|
435
530
|
maybeRunJournal({ activeSessionIds: getActiveSessionIds() }).catch(logBackgroundError("journal"));
|
|
436
531
|
maybeIndexSession({
|
|
@@ -4,6 +4,8 @@ import { writeWorkspaceText } from "../../utils/files/workspace-io.js";
|
|
|
4
4
|
import { buildArtifactPath } from "../../utils/files/naming.js";
|
|
5
5
|
import { errorMessage } from "../../utils/errors.js";
|
|
6
6
|
import { badRequest, serverError } from "../../utils/httpError.js";
|
|
7
|
+
import { log } from "../../system/logger/index.js";
|
|
8
|
+
import { previewSnippet } from "../../utils/logPreview.js";
|
|
7
9
|
import { isRecord } from "../../utils/types.js";
|
|
8
10
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
9
11
|
|
|
@@ -68,13 +70,22 @@ function isValidChartEntry(value: unknown): value is ChartEntry {
|
|
|
68
70
|
|
|
69
71
|
router.post(API_ROUTES.chart.present, async (req: Request<object, unknown, PresentChartBody>, res: Response<PresentChartResponse>) => {
|
|
70
72
|
const { document, title } = req.body;
|
|
73
|
+
log.info("chart", "present: start", {
|
|
74
|
+
titlePreview: typeof title === "string" ? previewSnippet(title) : undefined,
|
|
75
|
+
chartCount:
|
|
76
|
+
typeof document === "object" && document !== null && Array.isArray((document as { charts?: unknown[] }).charts)
|
|
77
|
+
? (document as { charts: unknown[] }).charts.length
|
|
78
|
+
: undefined,
|
|
79
|
+
});
|
|
71
80
|
|
|
72
81
|
if (!isValidChartDocument(document)) {
|
|
82
|
+
log.warn("chart", "present: invalid document shape");
|
|
73
83
|
badRequest(res, "document must be { charts: [{ option: {...}, title?, type? }, ...] } with at least one entry");
|
|
74
84
|
return;
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
if (title !== undefined && typeof title !== "string") {
|
|
88
|
+
log.warn("chart", "present: title must be string");
|
|
78
89
|
badRequest(res, "title must be a string when provided");
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
@@ -83,6 +94,7 @@ router.post(API_ROUTES.chart.present, async (req: Request<object, unknown, Prese
|
|
|
83
94
|
const baseLabel = title ?? document.title ?? "chart";
|
|
84
95
|
const filePath = buildArtifactPath(WORKSPACE_DIRS.charts, baseLabel, ".chart.json", "chart");
|
|
85
96
|
await writeWorkspaceText(filePath, `${JSON.stringify(document, null, 2)}\n`);
|
|
97
|
+
log.info("chart", "present: ok", { filePath, chartCount: document.charts.length });
|
|
86
98
|
res.json({
|
|
87
99
|
message: `Saved chart document to ${filePath}`,
|
|
88
100
|
instructions:
|
|
@@ -91,6 +103,7 @@ router.post(API_ROUTES.chart.present, async (req: Request<object, unknown, Prese
|
|
|
91
103
|
data: { document, title, filePath },
|
|
92
104
|
});
|
|
93
105
|
} catch (err) {
|
|
106
|
+
log.error("chart", "present: threw", { error: errorMessage(err) });
|
|
94
107
|
serverError(res, errorMessage(err));
|
|
95
108
|
}
|
|
96
109
|
});
|
|
@@ -13,6 +13,7 @@ import { Router, Request, Response } from "express";
|
|
|
13
13
|
import { backfillAllSessions } from "../../workspace/chat-index/index.js";
|
|
14
14
|
import { log } from "../../system/logger/index.js";
|
|
15
15
|
import { serverError } from "../../utils/httpError.js";
|
|
16
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
16
17
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
17
18
|
|
|
18
19
|
interface RebuildResponse {
|
|
@@ -39,7 +40,7 @@ router.post(API_ROUTES.chatIndex.rebuild, async (_req: Request, res: Response<Re
|
|
|
39
40
|
res.json(result);
|
|
40
41
|
} catch (err) {
|
|
41
42
|
log.warn("chat-index", "rebuild failed", { error: String(err) });
|
|
42
|
-
serverError(res, err
|
|
43
|
+
serverError(res, errorMessage(err, "unknown error"));
|
|
43
44
|
}
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -12,8 +12,10 @@ import {
|
|
|
12
12
|
type McpServerEntry,
|
|
13
13
|
} from "../../system/config.js";
|
|
14
14
|
import { badRequest, serverError } from "../../utils/httpError.js";
|
|
15
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
15
16
|
import { isRecord } from "../../utils/types.js";
|
|
16
17
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
18
|
+
import { log } from "../../system/logger/index.js";
|
|
17
19
|
import { loadCustomDirs, saveCustomDirs, ensureCustomDirs, validateCustomDirs, type CustomDirEntry } from "../../workspace/custom-dirs.js";
|
|
18
20
|
import { loadReferenceDirs, saveReferenceDirs, validateReferenceDirs, type ReferenceDirEntry } from "../../workspace/reference-dirs.js";
|
|
19
21
|
|
|
@@ -43,7 +45,7 @@ function isMcpPutBody(value: unknown): value is { servers: McpServerEntry[] } {
|
|
|
43
45
|
if (!Array.isArray(value.servers)) return false;
|
|
44
46
|
// Full shape validation happens inside fromMcpEntries (throws on
|
|
45
47
|
// anything malformed). Here we just confirm the envelope.
|
|
46
|
-
return value.servers.every((
|
|
48
|
+
return value.servers.every((entry) => isRecord(entry) && "id" in entry && "spec" in entry);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
// Parse an MCP payload through `fromMcpEntries` (which does the full
|
|
@@ -53,7 +55,7 @@ function parseMcpPayloadOrFail(res: ConfigRes, servers: McpServerEntry[]): McpCo
|
|
|
53
55
|
try {
|
|
54
56
|
return fromMcpEntries(servers);
|
|
55
57
|
} catch (err) {
|
|
56
|
-
badRequest(res, err
|
|
58
|
+
badRequest(res, errorMessage(err, "invalid mcp entries"));
|
|
57
59
|
return null;
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -66,7 +68,8 @@ function runSaveOrFail(res: ConfigRes, save: () => void, fallback: string): bool
|
|
|
66
68
|
save();
|
|
67
69
|
return true;
|
|
68
70
|
} catch (err) {
|
|
69
|
-
|
|
71
|
+
log.error("config", `save failed: ${fallback}`, { error: errorMessage(err) });
|
|
72
|
+
serverError(res, errorMessage(err, fallback));
|
|
70
73
|
return false;
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -94,7 +97,9 @@ function isPutConfigBody(value: unknown): value is PutConfigBody {
|
|
|
94
97
|
|
|
95
98
|
router.put(API_ROUTES.config.base, (req: Request<unknown, unknown, PutConfigBody>, res: ConfigRes) => {
|
|
96
99
|
const body = req.body;
|
|
100
|
+
log.info("config", "PUT base: start");
|
|
97
101
|
if (!isPutConfigBody(body)) {
|
|
102
|
+
log.warn("config", "PUT base: invalid payload");
|
|
98
103
|
badRequest(res, "Invalid config payload");
|
|
99
104
|
return;
|
|
100
105
|
}
|
|
@@ -113,29 +118,35 @@ router.put(API_ROUTES.config.base, (req: Request<unknown, unknown, PutConfigBody
|
|
|
113
118
|
// is already on the wire.
|
|
114
119
|
try {
|
|
115
120
|
saveSettings(previousSettings);
|
|
116
|
-
} catch {
|
|
117
|
-
|
|
121
|
+
} catch (err) {
|
|
122
|
+
log.error("config", "PUT base: rollback also failed", { error: errorMessage(err) });
|
|
118
123
|
}
|
|
119
124
|
return;
|
|
120
125
|
}
|
|
126
|
+
log.info("config", "PUT base: ok");
|
|
121
127
|
res.json(buildFullResponse());
|
|
122
128
|
});
|
|
123
129
|
|
|
124
130
|
router.put(API_ROUTES.config.settings, (req: Request<unknown, unknown, AppSettings>, res: ConfigRes) => {
|
|
125
131
|
const body = req.body;
|
|
132
|
+
log.info("config", "PUT settings: start");
|
|
126
133
|
if (!isAppSettings(body)) {
|
|
134
|
+
log.warn("config", "PUT settings: invalid payload");
|
|
127
135
|
badRequest(res, "Invalid AppSettings payload");
|
|
128
136
|
return;
|
|
129
137
|
}
|
|
130
138
|
if (!runSaveOrFail(res, () => saveSettings(body), "saveSettings failed")) {
|
|
131
139
|
return;
|
|
132
140
|
}
|
|
141
|
+
log.info("config", "PUT settings: ok");
|
|
133
142
|
res.json(buildFullResponse());
|
|
134
143
|
});
|
|
135
144
|
|
|
136
145
|
router.put(API_ROUTES.config.mcp, (req: Request<unknown, unknown, { servers: McpServerEntry[] }>, res: ConfigRes) => {
|
|
137
146
|
const body = req.body;
|
|
147
|
+
log.info("config", "PUT mcp: start", { servers: Array.isArray(body?.servers) ? body.servers.length : undefined });
|
|
138
148
|
if (!isMcpPutBody(body)) {
|
|
149
|
+
log.warn("config", "PUT mcp: invalid envelope");
|
|
139
150
|
badRequest(res, "Invalid mcp payload envelope");
|
|
140
151
|
return;
|
|
141
152
|
}
|
|
@@ -146,6 +157,7 @@ router.put(API_ROUTES.config.mcp, (req: Request<unknown, unknown, { servers: Mcp
|
|
|
146
157
|
if (!runSaveOrFail(res, () => saveMcpConfig(cfg), "saveMcpConfig failed")) {
|
|
147
158
|
return;
|
|
148
159
|
}
|
|
160
|
+
log.info("config", "PUT mcp: ok", { servers: body.servers.length });
|
|
149
161
|
res.json(buildFullResponse());
|
|
150
162
|
});
|
|
151
163
|
|
|
@@ -159,21 +171,26 @@ router.put(
|
|
|
159
171
|
API_ROUTES.config.workspaceDirs,
|
|
160
172
|
(req: Request<unknown, unknown, { dirs: unknown }>, res: Response<{ dirs: CustomDirEntry[] } | ConfigErrorResponse>) => {
|
|
161
173
|
const body = req.body;
|
|
174
|
+
log.info("config", "PUT workspace-dirs: start");
|
|
162
175
|
if (!isRecord(body) || !("dirs" in body)) {
|
|
176
|
+
log.warn("config", "PUT workspace-dirs: invalid envelope");
|
|
163
177
|
badRequest(res, "expected { dirs: [...] }");
|
|
164
178
|
return;
|
|
165
179
|
}
|
|
166
180
|
const result = validateCustomDirs(body.dirs);
|
|
167
181
|
if ("error" in result) {
|
|
182
|
+
log.warn("config", "PUT workspace-dirs: validation failed", { error: result.error });
|
|
168
183
|
badRequest(res, result.error);
|
|
169
184
|
return;
|
|
170
185
|
}
|
|
171
186
|
try {
|
|
172
187
|
saveCustomDirs(result.entries);
|
|
173
188
|
ensureCustomDirs(result.entries);
|
|
189
|
+
log.info("config", "PUT workspace-dirs: ok", { dirs: result.entries.length });
|
|
174
190
|
res.json({ dirs: result.entries });
|
|
175
191
|
} catch (err) {
|
|
176
|
-
|
|
192
|
+
log.error("config", "PUT workspace-dirs: threw", { error: errorMessage(err) });
|
|
193
|
+
serverError(res, errorMessage(err, "save failed"));
|
|
177
194
|
}
|
|
178
195
|
},
|
|
179
196
|
);
|
|
@@ -188,20 +205,25 @@ router.put(
|
|
|
188
205
|
API_ROUTES.config.referenceDirs,
|
|
189
206
|
(req: Request<unknown, unknown, { dirs: unknown }>, res: Response<{ dirs: ReferenceDirEntry[] } | ConfigErrorResponse>) => {
|
|
190
207
|
const body = req.body;
|
|
208
|
+
log.info("config", "PUT reference-dirs: start");
|
|
191
209
|
if (!isRecord(body) || !("dirs" in body)) {
|
|
210
|
+
log.warn("config", "PUT reference-dirs: invalid envelope");
|
|
192
211
|
badRequest(res, "expected { dirs: [...] }");
|
|
193
212
|
return;
|
|
194
213
|
}
|
|
195
214
|
const result = validateReferenceDirs(body.dirs);
|
|
196
215
|
if ("error" in result) {
|
|
216
|
+
log.warn("config", "PUT reference-dirs: validation failed", { error: result.error });
|
|
197
217
|
badRequest(res, result.error);
|
|
198
218
|
return;
|
|
199
219
|
}
|
|
200
220
|
try {
|
|
201
221
|
saveReferenceDirs(result.entries);
|
|
222
|
+
log.info("config", "PUT reference-dirs: ok", { dirs: result.entries.length });
|
|
202
223
|
res.json({ dirs: result.entries });
|
|
203
224
|
} catch (err) {
|
|
204
|
-
|
|
225
|
+
log.error("config", "PUT reference-dirs: threw", { error: errorMessage(err) });
|
|
226
|
+
serverError(res, errorMessage(err, "save failed"));
|
|
205
227
|
}
|
|
206
228
|
},
|
|
207
229
|
);
|
|
@@ -220,12 +242,15 @@ router.put(
|
|
|
220
242
|
API_ROUTES.config.schedulerOverrides,
|
|
221
243
|
async (req: Request<unknown, unknown, { overrides: unknown }>, res: Response<{ overrides: ScheduleOverrides } | ConfigErrorResponse>) => {
|
|
222
244
|
const body = req.body;
|
|
245
|
+
log.info("config", "PUT scheduler-overrides: start");
|
|
223
246
|
if (!isRecord(body) || !("overrides" in body)) {
|
|
247
|
+
log.warn("config", "PUT scheduler-overrides: invalid envelope");
|
|
224
248
|
badRequest(res, "expected { overrides: { ... } }");
|
|
225
249
|
return;
|
|
226
250
|
}
|
|
227
251
|
const raw = body.overrides;
|
|
228
252
|
if (!isRecord(raw)) {
|
|
253
|
+
log.warn("config", "PUT scheduler-overrides: overrides not an object");
|
|
229
254
|
badRequest(res, "overrides must be an object");
|
|
230
255
|
return;
|
|
231
256
|
}
|
|
@@ -248,9 +273,11 @@ router.put(
|
|
|
248
273
|
}
|
|
249
274
|
}
|
|
250
275
|
|
|
276
|
+
log.info("config", "PUT scheduler-overrides: ok", { tasks: Object.keys(overrides).length });
|
|
251
277
|
res.json({ overrides: loadSchedulerOverrides() });
|
|
252
278
|
} catch (err) {
|
|
253
|
-
|
|
279
|
+
log.error("config", "PUT scheduler-overrides: threw", { error: errorMessage(err) });
|
|
280
|
+
serverError(res, errorMessage(err, "save failed"));
|
|
254
281
|
}
|
|
255
282
|
},
|
|
256
283
|
);
|