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
|
@@ -7,8 +7,8 @@ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
|
7
7
|
const router = Router();
|
|
8
8
|
|
|
9
9
|
async function callGemini(prompt: string): Promise<string> {
|
|
10
|
-
const
|
|
11
|
-
const response = await
|
|
10
|
+
const client = getGeminiClient();
|
|
11
|
+
const response = await client.models.generateContent({
|
|
12
12
|
model: "gemini-2.0-flash",
|
|
13
13
|
contents: [{ text: prompt }],
|
|
14
14
|
});
|
|
@@ -4,7 +4,7 @@ import { getSessionImageData } from "../../events/session-store/index.js";
|
|
|
4
4
|
import { generateGeminiImageContent, generateGeminiImageFromPrompt } from "../../utils/gemini.js";
|
|
5
5
|
import { errorMessage } from "../../utils/errors.js";
|
|
6
6
|
import { badRequest, serverError } from "../../utils/httpError.js";
|
|
7
|
-
import { saveImage, overwriteImage, loadImageBase64, stripDataUri, isImagePath } from "../../utils/files/image-store.js";
|
|
7
|
+
import { saveImage, overwriteImage, loadImageBase64, stripDataUri, isImagePath, imagePathFromFilename } from "../../utils/files/image-store.js";
|
|
8
8
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
9
9
|
|
|
10
10
|
const router = Router();
|
|
@@ -148,14 +148,14 @@ router.post(API_ROUTES.image.upload, async (req: Request<object, unknown, Canvas
|
|
|
148
148
|
router.put(
|
|
149
149
|
API_ROUTES.image.update,
|
|
150
150
|
async (req: Request<{ filename: string }, unknown, CanvasImageBody>, res: Response<CanvasImageResponse | CanvasImageError>) => {
|
|
151
|
-
const relativePath =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
badRequest(res, "imageData and path are required");
|
|
151
|
+
const relativePath = imagePathFromFilename(req.params.filename);
|
|
152
|
+
if (!relativePath) {
|
|
153
|
+
badRequest(res, "invalid image filename");
|
|
155
154
|
return;
|
|
156
155
|
}
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
const { imageData } = req.body;
|
|
157
|
+
if (!imageData) {
|
|
158
|
+
badRequest(res, "imageData is required");
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
const base64 = stripDataUri(imageData);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Router, Request, Response } from "express";
|
|
2
|
-
import
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { WORKSPACE_PATHS } from "../../workspace/paths.js";
|
|
5
5
|
import { stripDataUri } from "../../utils/files/image-store.js";
|
|
6
|
+
import { writeJsonAtomic } from "../../utils/files/json.js";
|
|
6
7
|
import {
|
|
7
8
|
getFileObject,
|
|
8
9
|
initializeContextFromFiles,
|
|
@@ -26,6 +27,7 @@ import { slugify } from "../../utils/slug.js";
|
|
|
26
27
|
import { resolveWithinRoot } from "../../utils/files/safe.js";
|
|
27
28
|
import { errorMessage } from "../../utils/errors.js";
|
|
28
29
|
import { badRequest, notFound, serverError } from "../../utils/httpError.js";
|
|
30
|
+
import { getOptionalStringQuery } from "../../utils/request.js";
|
|
29
31
|
import { log } from "../../system/logger/index.js";
|
|
30
32
|
import { validateUpdateBeatBody, validateUpdateScriptBody } from "./mulmoScriptValidate.js";
|
|
31
33
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
@@ -54,8 +56,8 @@ let storiesRealCache: string | null = null;
|
|
|
54
56
|
function ensureStoriesReal(): string | null {
|
|
55
57
|
if (storiesRealCache) return storiesRealCache;
|
|
56
58
|
try {
|
|
57
|
-
|
|
58
|
-
storiesRealCache =
|
|
59
|
+
mkdirSync(storiesDir, { recursive: true });
|
|
60
|
+
storiesRealCache = realpathSync(storiesDir);
|
|
59
61
|
return storiesRealCache;
|
|
60
62
|
} catch {
|
|
61
63
|
return null;
|
|
@@ -96,7 +98,7 @@ interface FilePathQuery {
|
|
|
96
98
|
filePath?: string;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
router.post(API_ROUTES.mulmoScript.save, (req: Request<object, object, SaveMulmoScriptBody>, res: Response) => {
|
|
101
|
+
router.post(API_ROUTES.mulmoScript.save, async (req: Request<object, object, SaveMulmoScriptBody>, res: Response) => {
|
|
100
102
|
const { script, filename } = req.body;
|
|
101
103
|
|
|
102
104
|
if (!script || !Array.isArray(script.beats)) {
|
|
@@ -104,14 +106,14 @@ router.post(API_ROUTES.mulmoScript.save, (req: Request<object, object, SaveMulmo
|
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
mkdirSync(storiesDir, { recursive: true });
|
|
108
110
|
|
|
109
111
|
const title = script.title || "untitled";
|
|
110
112
|
const slug = filename ? filename.replace(/\.json$/, "") : slugify(title);
|
|
111
113
|
const fname = `${slug}-${Date.now()}.json`;
|
|
112
114
|
const filePath = path.join(storiesDir, fname);
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
await writeJsonAtomic(filePath, script);
|
|
115
117
|
|
|
116
118
|
res.json({
|
|
117
119
|
data: { script, filePath: `stories/${fname}` },
|
|
@@ -120,7 +122,7 @@ router.post(API_ROUTES.mulmoScript.save, (req: Request<object, object, SaveMulmo
|
|
|
120
122
|
});
|
|
121
123
|
});
|
|
122
124
|
|
|
123
|
-
router.post(API_ROUTES.mulmoScript.updateBeat, (req: Request<object, object, unknown>, res: Response) => {
|
|
125
|
+
router.post(API_ROUTES.mulmoScript.updateBeat, async (req: Request<object, object, unknown>, res: Response) => {
|
|
124
126
|
const validation = validateUpdateBeatBody(req.body);
|
|
125
127
|
if (!validation.ok) {
|
|
126
128
|
badRequest(res, validation.error);
|
|
@@ -131,7 +133,7 @@ router.post(API_ROUTES.mulmoScript.updateBeat, (req: Request<object, object, unk
|
|
|
131
133
|
const absoluteFilePath = resolveStoryPath(filePath, res);
|
|
132
134
|
if (!absoluteFilePath) return;
|
|
133
135
|
|
|
134
|
-
const script: MulmoScript = JSON.parse(
|
|
136
|
+
const script: MulmoScript = JSON.parse(readFileSync(absoluteFilePath, "utf-8"));
|
|
135
137
|
|
|
136
138
|
if (!Array.isArray(script.beats) || beatIndex >= script.beats.length) {
|
|
137
139
|
badRequest(res, "Invalid beatIndex");
|
|
@@ -139,12 +141,12 @@ router.post(API_ROUTES.mulmoScript.updateBeat, (req: Request<object, object, unk
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
script.beats[beatIndex] = beat as MulmoBeat;
|
|
142
|
-
|
|
144
|
+
await writeJsonAtomic(absoluteFilePath, script);
|
|
143
145
|
|
|
144
146
|
res.json({ ok: true });
|
|
145
147
|
});
|
|
146
148
|
|
|
147
|
-
router.post(API_ROUTES.mulmoScript.updateScript, (req: Request<object, object, unknown>, res: Response) => {
|
|
149
|
+
router.post(API_ROUTES.mulmoScript.updateScript, async (req: Request<object, object, unknown>, res: Response) => {
|
|
148
150
|
const validation = validateUpdateScriptBody(req.body);
|
|
149
151
|
if (!validation.ok) {
|
|
150
152
|
badRequest(res, validation.error);
|
|
@@ -155,7 +157,7 @@ router.post(API_ROUTES.mulmoScript.updateScript, (req: Request<object, object, u
|
|
|
155
157
|
const absoluteFilePath = resolveStoryPath(filePath, res);
|
|
156
158
|
if (!absoluteFilePath) return;
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
await writeJsonAtomic(absoluteFilePath, updatedScript);
|
|
159
161
|
res.json({ ok: true });
|
|
160
162
|
});
|
|
161
163
|
|
|
@@ -170,7 +172,7 @@ router.get(API_ROUTES.mulmoScript.beatImage, async (req: Request<object, BeatIma
|
|
|
170
172
|
|
|
171
173
|
await withStoryContext(res, filePath, {}, async ({ context }) => {
|
|
172
174
|
const { imagePath } = getBeatPngImagePath(context, beatIndex);
|
|
173
|
-
if (!
|
|
175
|
+
if (!existsSync(imagePath)) {
|
|
174
176
|
res.json({ image: null });
|
|
175
177
|
return;
|
|
176
178
|
}
|
|
@@ -197,13 +199,13 @@ router.get(API_ROUTES.mulmoScript.movieStatus, async (req: Request<object, Movie
|
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
const outputPath = movieFilePath(context);
|
|
200
|
-
if (!
|
|
202
|
+
if (!existsSync(outputPath)) {
|
|
201
203
|
res.json({ moviePath: null });
|
|
202
204
|
return;
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
const movieMtime =
|
|
206
|
-
const sourceMtime =
|
|
207
|
+
const movieMtime = statSync(outputPath).mtimeMs;
|
|
208
|
+
const sourceMtime = statSync(absoluteFilePath).mtimeMs;
|
|
207
209
|
if (movieMtime < sourceMtime) {
|
|
208
210
|
res.json({ moviePath: null });
|
|
209
211
|
return;
|
|
@@ -216,7 +218,7 @@ router.get(API_ROUTES.mulmoScript.movieStatus, async (req: Request<object, Movie
|
|
|
216
218
|
});
|
|
217
219
|
|
|
218
220
|
function fileToDataUri(filePath: string, mimeType: string): string {
|
|
219
|
-
const data =
|
|
221
|
+
const data = readFileSync(filePath);
|
|
220
222
|
return `data:${mimeType};base64,${data.toString("base64")}`;
|
|
221
223
|
}
|
|
222
224
|
|
|
@@ -257,7 +259,7 @@ function resolveStoryPath(filePath: string, res: Response): string | null {
|
|
|
257
259
|
const resolved = resolveWithinRoot(storiesReal, relFromStories);
|
|
258
260
|
if (!resolved) {
|
|
259
261
|
const candidate = path.resolve(storiesReal, relFromStories);
|
|
260
|
-
if (!
|
|
262
|
+
if (!existsSync(candidate)) {
|
|
261
263
|
notFound(res, `File not found: ${filePath}`);
|
|
262
264
|
} else {
|
|
263
265
|
badRequest(res, "Invalid filePath");
|
|
@@ -373,12 +375,12 @@ router.get(API_ROUTES.mulmoScript.beatAudio, async (req: Request<object, BeatAud
|
|
|
373
375
|
filePath,
|
|
374
376
|
{
|
|
375
377
|
operation: "beat-audio",
|
|
376
|
-
onContextMissing: (
|
|
378
|
+
onContextMissing: (response) => response.json({ audio: null }),
|
|
377
379
|
},
|
|
378
380
|
async ({ context }) => {
|
|
379
381
|
const beat = context.studio.script.beats[beatIndex];
|
|
380
382
|
const audioPath = getBeatAudioPathOrUrl(beat.text ?? "", context, beat, context.lang);
|
|
381
|
-
if (!audioPath || !
|
|
383
|
+
if (!audioPath || !existsSync(audioPath)) {
|
|
382
384
|
res.json({ audio: null });
|
|
383
385
|
return;
|
|
384
386
|
}
|
|
@@ -422,7 +424,7 @@ router.post(
|
|
|
422
424
|
const beat = context.studio.script.beats[beatIndex];
|
|
423
425
|
const audioPath = context.studio.beats[beatIndex]?.audioFile ?? getBeatAudioPathOrUrl(beat.text ?? "", context, beat, context.lang);
|
|
424
426
|
|
|
425
|
-
if (!audioPath || !
|
|
427
|
+
if (!audioPath || !existsSync(audioPath)) {
|
|
426
428
|
// Logic-flow failure (not an exception) — emit a targeted
|
|
427
429
|
// log. Don't write raw `beat.text` into persistent logs —
|
|
428
430
|
// it's free-form user content and can contain sensitive
|
|
@@ -430,7 +432,7 @@ router.post(
|
|
|
430
432
|
log.error("generate-beat-audio", "audio was not generated", {
|
|
431
433
|
beatIndex,
|
|
432
434
|
audioPath,
|
|
433
|
-
exists: audioPath ?
|
|
435
|
+
exists: audioPath ? existsSync(audioPath) : false,
|
|
434
436
|
beatTextLength: typeof beat?.text === "string" ? beat.text.length : 0,
|
|
435
437
|
audioFilePresent: Boolean(context.studio.beats[beatIndex]?.audioFile),
|
|
436
438
|
});
|
|
@@ -475,7 +477,7 @@ router.post(API_ROUTES.mulmoScript.renderBeat, async (req: Request<object, objec
|
|
|
475
477
|
});
|
|
476
478
|
|
|
477
479
|
const { imagePath } = getBeatPngImagePath(context, beatIndex);
|
|
478
|
-
if (!
|
|
480
|
+
if (!existsSync(imagePath)) {
|
|
479
481
|
genError = "Image was not generated";
|
|
480
482
|
serverError(res, genError);
|
|
481
483
|
return;
|
|
@@ -542,7 +544,7 @@ router.post(API_ROUTES.mulmoScript.generateMovie, async (req: Request<object, ob
|
|
|
542
544
|
await movie(audioContext);
|
|
543
545
|
|
|
544
546
|
const outputPath = movieFilePath(audioContext);
|
|
545
|
-
if (!
|
|
547
|
+
if (!existsSync(outputPath)) {
|
|
546
548
|
genError = "Movie was not generated";
|
|
547
549
|
send({ type: "error", message: genError });
|
|
548
550
|
res.end();
|
|
@@ -594,7 +596,7 @@ router.get(
|
|
|
594
596
|
|
|
595
597
|
await withStoryContext(res, filePath, {}, async ({ context }) => {
|
|
596
598
|
const imagePath = getReferenceImagePath(context, key, "png");
|
|
597
|
-
if (!
|
|
599
|
+
if (!existsSync(imagePath)) {
|
|
598
600
|
res.json({ image: null });
|
|
599
601
|
return;
|
|
600
602
|
}
|
|
@@ -613,10 +615,10 @@ router.post(API_ROUTES.mulmoScript.uploadBeatImage, async (req: Request<object,
|
|
|
613
615
|
|
|
614
616
|
await withStoryContext(res, filePath, {}, async ({ context }) => {
|
|
615
617
|
const { imagePath } = getBeatPngImagePath(context, beatIndex);
|
|
616
|
-
|
|
618
|
+
mkdirSync(path.dirname(imagePath), { recursive: true });
|
|
617
619
|
|
|
618
620
|
const base64 = stripDataUri(imageData);
|
|
619
|
-
|
|
621
|
+
writeFileSync(imagePath, Buffer.from(base64, "base64"));
|
|
620
622
|
|
|
621
623
|
res.json({ image: fileToDataUri(imagePath, "image/png") });
|
|
622
624
|
});
|
|
@@ -647,7 +649,7 @@ router.post(
|
|
|
647
649
|
|
|
648
650
|
const index = Object.keys(images).indexOf(key);
|
|
649
651
|
const imagePath = getReferenceImagePath(context, key, "png");
|
|
650
|
-
|
|
652
|
+
mkdirSync(path.dirname(imagePath), { recursive: true });
|
|
651
653
|
|
|
652
654
|
await generateReferenceImage({
|
|
653
655
|
context,
|
|
@@ -656,7 +658,7 @@ router.post(
|
|
|
656
658
|
image: imageEntry as MulmoImagePromptMedia,
|
|
657
659
|
force,
|
|
658
660
|
});
|
|
659
|
-
if (!
|
|
661
|
+
if (!existsSync(imagePath)) {
|
|
660
662
|
genError = "Character image was not generated";
|
|
661
663
|
serverError(res, genError);
|
|
662
664
|
return;
|
|
@@ -685,10 +687,10 @@ router.post(
|
|
|
685
687
|
|
|
686
688
|
await withStoryContext(res, filePath, {}, async ({ context }) => {
|
|
687
689
|
const imagePath = getReferenceImagePath(context, key, "png");
|
|
688
|
-
|
|
690
|
+
mkdirSync(path.dirname(imagePath), { recursive: true });
|
|
689
691
|
|
|
690
692
|
const base64 = stripDataUri(imageData);
|
|
691
|
-
|
|
693
|
+
writeFileSync(imagePath, Buffer.from(base64, "base64"));
|
|
692
694
|
|
|
693
695
|
res.json({ image: fileToDataUri(imagePath, "image/png") });
|
|
694
696
|
});
|
|
@@ -696,7 +698,7 @@ router.post(
|
|
|
696
698
|
);
|
|
697
699
|
|
|
698
700
|
router.get(API_ROUTES.mulmoScript.downloadMovie, (req: Request, res: Response) => {
|
|
699
|
-
const moviePath =
|
|
701
|
+
const moviePath = getOptionalStringQuery(req, "moviePath");
|
|
700
702
|
|
|
701
703
|
if (!moviePath) {
|
|
702
704
|
badRequest(res, "moviePath is required");
|
package/server/api/routes/pdf.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { realpathSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Router, Request, Response } from "express";
|
|
4
4
|
import { marked } from "marked";
|
|
@@ -56,7 +56,7 @@ const MIME_BY_EXT: Record<string, string> = {
|
|
|
56
56
|
// Realpath of the workspace, resolved once at module load. Used to
|
|
57
57
|
// validate that image paths resolved relative to markdowns/ stay
|
|
58
58
|
// inside the workspace after symlink resolution.
|
|
59
|
-
const workspaceReal =
|
|
59
|
+
const workspaceReal = realpathSync(resolveWorkspacePath(""));
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Inline local images as base64 data URIs so Puppeteer can render them.
|
|
@@ -80,10 +80,10 @@ async function fillImagePlaceholders(markdown: string): Promise<string> {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const results = await Promise.all(
|
|
83
|
-
matches.map(async (
|
|
84
|
-
full:
|
|
85
|
-
prompt:
|
|
86
|
-
url: geminiOk ? await generateImageFile(
|
|
83
|
+
matches.map(async (match) => ({
|
|
84
|
+
full: match[0],
|
|
85
|
+
prompt: match[1],
|
|
86
|
+
url: geminiOk ? await generateImageFile(match[1]) : null,
|
|
87
87
|
})),
|
|
88
88
|
);
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ async function fillImagePlaceholders(markdown: string): Promise<string> {
|
|
|
91
91
|
// success rate even when most calls go through. The per-call
|
|
92
92
|
// error already lands at warn from generateImageFile's catch.
|
|
93
93
|
if (geminiOk) {
|
|
94
|
-
const failed = results.filter((
|
|
94
|
+
const failed = results.filter((result) => !result.url).length;
|
|
95
95
|
if (failed > 0) {
|
|
96
96
|
log.warn("present-document", "image generation had failures", {
|
|
97
97
|
failed,
|
|
@@ -261,10 +261,20 @@ router.post(
|
|
|
261
261
|
wrapPluginExecute((req) => executeForm(null as never, req.body)),
|
|
262
262
|
);
|
|
263
263
|
|
|
264
|
+
// 1×1 transparent PNG. Used as a placeholder so the canvas tool
|
|
265
|
+
// result can carry a stable file path from the moment the canvas
|
|
266
|
+
// is opened — client autosaves PUT-overwrite this same file, so the
|
|
267
|
+
// drawing survives page reload with zero client→server sync.
|
|
268
|
+
const BLANK_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
|
|
269
|
+
|
|
264
270
|
// openCanvas — drawing canvas
|
|
265
271
|
router.post(
|
|
266
272
|
API_ROUTES.plugins.canvas,
|
|
267
|
-
wrapPluginExecute(() =>
|
|
273
|
+
wrapPluginExecute(async () => {
|
|
274
|
+
const imagePath = await saveImage(BLANK_PNG_BASE64);
|
|
275
|
+
const base = await executeOpenCanvas();
|
|
276
|
+
return { ...base, data: { imageData: imagePath, prompt: "" } };
|
|
277
|
+
}),
|
|
268
278
|
);
|
|
269
279
|
|
|
270
280
|
// present3d — 3D visualization
|
|
@@ -7,7 +7,7 @@ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
|
7
7
|
import { EVENT_TYPES } from "../../../src/types/events.js";
|
|
8
8
|
import { roleExists, deleteRole, saveRole } from "../../utils/files/roles-io.js";
|
|
9
9
|
|
|
10
|
-
const BUILTIN_IDS = new Set(BUILTIN_ROLES.map((
|
|
10
|
+
const BUILTIN_IDS = new Set(BUILTIN_ROLES.map((role) => role.id));
|
|
11
11
|
|
|
12
12
|
const router = Router();
|
|
13
13
|
|
|
@@ -98,7 +98,7 @@ function saveRoleResult(input: ManageRolesInput, sessionId: string): Record<stri
|
|
|
98
98
|
const pluginsToSave = role.availablePlugins ?? [];
|
|
99
99
|
const roleToSave = {
|
|
100
100
|
...role,
|
|
101
|
-
availablePlugins: pluginsToSave.filter((
|
|
101
|
+
availablePlugins: pluginsToSave.filter((plugin) => plugin !== "switchRole"),
|
|
102
102
|
};
|
|
103
103
|
|
|
104
104
|
saveRole(role.id, roleToSave);
|
|
@@ -9,6 +9,8 @@ import { saveUserTasks } from "../../utils/files/user-tasks-io.js";
|
|
|
9
9
|
import { startChat } from "./agent.js";
|
|
10
10
|
import { log } from "../../system/logger/index.js";
|
|
11
11
|
import { SCHEDULER_ACTIONS, TASK_ACTIONS } from "../../../src/config/schedulerActions.js";
|
|
12
|
+
import { badRequest, notFound, serverError } from "../../utils/httpError.js";
|
|
13
|
+
import { errorMessage } from "../../utils/errors.js";
|
|
12
14
|
|
|
13
15
|
const router = Router();
|
|
14
16
|
|
|
@@ -77,7 +79,7 @@ async function handleTaskAction(action: string, input: Record<string, unknown>,
|
|
|
77
79
|
if (action === SCHEDULER_ACTIONS.createTask) {
|
|
78
80
|
const result = validateAndCreate(input);
|
|
79
81
|
if (result.kind === "error") {
|
|
80
|
-
res
|
|
82
|
+
badRequest(res, result.error);
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
85
|
const tasks = loadUserTasks();
|
|
@@ -93,11 +95,11 @@ async function handleTaskAction(action: string, input: Record<string, unknown>,
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
if (action === SCHEDULER_ACTIONS.deleteTask) {
|
|
96
|
-
const
|
|
98
|
+
const taskId = typeof input.id === "string" ? input.id : "";
|
|
97
99
|
const tasks = loadUserTasks();
|
|
98
|
-
const idx = tasks.findIndex((
|
|
100
|
+
const idx = tasks.findIndex((task) => task.id === taskId);
|
|
99
101
|
if (idx === -1) {
|
|
100
|
-
res
|
|
102
|
+
notFound(res, `task not found: ${taskId}`);
|
|
101
103
|
return;
|
|
102
104
|
}
|
|
103
105
|
const name = tasks[idx].name;
|
|
@@ -107,17 +109,17 @@ async function handleTaskAction(action: string, input: Record<string, unknown>,
|
|
|
107
109
|
res.json({
|
|
108
110
|
uuid: crypto.randomUUID(),
|
|
109
111
|
message: `Task "${name}" deleted.`,
|
|
110
|
-
data: { deleted:
|
|
112
|
+
data: { deleted: taskId },
|
|
111
113
|
});
|
|
112
114
|
return;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
if (action === SCHEDULER_ACTIONS.runTask) {
|
|
116
|
-
const
|
|
118
|
+
const taskId = typeof input.id === "string" ? input.id : "";
|
|
117
119
|
const tasks = loadUserTasks();
|
|
118
|
-
const task = tasks.find((
|
|
120
|
+
const task = tasks.find((candidate) => candidate.id === taskId);
|
|
119
121
|
if (!task) {
|
|
120
|
-
res
|
|
122
|
+
notFound(res, `task not found: ${taskId}`);
|
|
121
123
|
return;
|
|
122
124
|
}
|
|
123
125
|
const chatSessionId = crypto.randomUUID();
|
|
@@ -138,15 +140,15 @@ async function handleTaskAction(action: string, input: Record<string, unknown>,
|
|
|
138
140
|
res.json({
|
|
139
141
|
uuid: crypto.randomUUID(),
|
|
140
142
|
message: `Task "${task.name}" triggered.`,
|
|
141
|
-
data: { triggered:
|
|
143
|
+
data: { triggered: taskId, chatSessionId },
|
|
142
144
|
});
|
|
143
145
|
return;
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
res
|
|
148
|
+
badRequest(res, `unknown task action: ${action}`);
|
|
147
149
|
} catch (err) {
|
|
148
|
-
log.error("scheduler", "task action failed", { error:
|
|
149
|
-
res
|
|
150
|
+
log.error("scheduler", "task action failed", { error: errorMessage(err) });
|
|
151
|
+
serverError(res, "Internal server error");
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
|
|
@@ -29,14 +29,14 @@ export type SchedulerActionResult =
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
export function sortItems(items: ScheduledItem[]): ScheduledItem[] {
|
|
32
|
-
return [...items].sort((
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
return
|
|
32
|
+
return [...items].sort((left, right) => {
|
|
33
|
+
const leftDate = typeof left.props.date === "string" ? left.props.date : null;
|
|
34
|
+
const rightDate = typeof right.props.date === "string" ? right.props.date : null;
|
|
35
|
+
const leftTime = typeof left.props.time === "string" ? left.props.time : "00:00";
|
|
36
|
+
const rightTime = typeof right.props.time === "string" ? right.props.time : "00:00";
|
|
37
|
+
const leftKey = leftDate ? `0_${leftDate}_${leftTime}` : `1_${left.createdAt}`;
|
|
38
|
+
const rightKey = rightDate ? `0_${rightDate}_${rightTime}` : `1_${right.createdAt}`;
|
|
39
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -84,11 +84,11 @@ export function handleDelete(items: ScheduledItem[], input: SchedulerActionInput
|
|
|
84
84
|
|
|
85
85
|
function applyPropPatch(current: ScheduledItem["props"], patch: Record<string, string | number | boolean | null>): ScheduledItem["props"] {
|
|
86
86
|
const next: ScheduledItem["props"] = { ...current };
|
|
87
|
-
for (const [
|
|
88
|
-
if (
|
|
89
|
-
delete next[
|
|
87
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
88
|
+
if (value === null) {
|
|
89
|
+
delete next[key];
|
|
90
90
|
} else {
|
|
91
|
-
next[
|
|
91
|
+
next[key] = value;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
return next;
|
|
@@ -15,6 +15,7 @@ import { SESSION_ORIGINS } from "../../../src/types/session.js";
|
|
|
15
15
|
import { loadUserTasks, validateAndCreate, applyUpdate, withUserTaskLock } from "../../workspace/skills/user-tasks.js";
|
|
16
16
|
import { badRequest, notFound, serverError } from "../../utils/httpError.js";
|
|
17
17
|
import { errorMessage } from "../../utils/errors.js";
|
|
18
|
+
import { getOptionalStringQuery } from "../../utils/request.js";
|
|
18
19
|
import { log } from "../../system/logger/index.js";
|
|
19
20
|
import { startChat } from "./agent.js";
|
|
20
21
|
|
|
@@ -29,7 +30,7 @@ router.get(API_ROUTES.scheduler.tasks, (_req: Request, res: Response) => {
|
|
|
29
30
|
// have no origin field of their own.
|
|
30
31
|
const systemTasks = getSchedulerTasks();
|
|
31
32
|
const userTasks = loadUserTasks();
|
|
32
|
-
const all = [...systemTasks.map((
|
|
33
|
+
const all = [...systemTasks.map((task) => ({ ...task, origin: "system" as const })), ...userTasks.map((task) => ({ ...task, origin: "user" as const }))];
|
|
33
34
|
res.json({ tasks: all });
|
|
34
35
|
});
|
|
35
36
|
|
|
@@ -58,14 +59,14 @@ router.post(API_ROUTES.scheduler.tasks, async (req: Request, res: Response) => {
|
|
|
58
59
|
// ── Update user task ────────────────────────────────────────────
|
|
59
60
|
|
|
60
61
|
router.put(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, res: Response) => {
|
|
61
|
-
const { id } = req.params;
|
|
62
|
+
const { id: taskId } = req.params;
|
|
62
63
|
try {
|
|
63
64
|
const updated = await withUserTaskLock(async (tasks) => {
|
|
64
|
-
const result = applyUpdate(tasks,
|
|
65
|
+
const result = applyUpdate(tasks, taskId, req.body);
|
|
65
66
|
if (result.kind === "error") {
|
|
66
67
|
throw new Error(result.error);
|
|
67
68
|
}
|
|
68
|
-
const task = result.tasks.find((
|
|
69
|
+
const task = result.tasks.find((taskItem) => taskItem.id === taskId);
|
|
69
70
|
return { tasks: result.tasks, result: task };
|
|
70
71
|
});
|
|
71
72
|
res.json({ task: updated });
|
|
@@ -83,15 +84,15 @@ router.put(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, res:
|
|
|
83
84
|
// ── Delete user task ────────────────────────────────────────────
|
|
84
85
|
|
|
85
86
|
router.delete(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, res: Response) => {
|
|
86
|
-
const { id } = req.params;
|
|
87
|
+
const { id: taskId } = req.params;
|
|
87
88
|
try {
|
|
88
89
|
await withUserTaskLock(async (tasks) => {
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
const next = tasks.filter((
|
|
90
|
+
const index = tasks.findIndex((task) => task.id === taskId);
|
|
91
|
+
if (index === -1) throw new Error(`task not found: ${taskId}`);
|
|
92
|
+
const next = tasks.filter((task) => task.id !== taskId);
|
|
92
93
|
return { tasks: next, result: undefined };
|
|
93
94
|
});
|
|
94
|
-
res.json({ deleted:
|
|
95
|
+
res.json({ deleted: taskId });
|
|
95
96
|
} catch (err) {
|
|
96
97
|
const msg = errorMessage(err);
|
|
97
98
|
if (msg.startsWith("task not found")) {
|
|
@@ -106,10 +107,10 @@ router.delete(API_ROUTES.scheduler.task, async (req: Request<{ id: string }>, re
|
|
|
106
107
|
// ── Manual trigger ──────────────────────────────────────────────
|
|
107
108
|
|
|
108
109
|
router.post(API_ROUTES.scheduler.taskRun, async (req: Request<{ id: string }>, res: Response) => {
|
|
109
|
-
const { id } = req.params;
|
|
110
|
+
const { id: taskId } = req.params;
|
|
110
111
|
// Check user tasks first
|
|
111
112
|
const userTasks = loadUserTasks();
|
|
112
|
-
const userTask = userTasks.find((
|
|
113
|
+
const userTask = userTasks.find((task) => task.id === taskId);
|
|
113
114
|
if (userTask) {
|
|
114
115
|
const chatSessionId = crypto.randomUUID();
|
|
115
116
|
log.info("scheduler-tasks", "manual run (user task)", {
|
|
@@ -126,14 +127,14 @@ router.post(API_ROUTES.scheduler.taskRun, async (req: Request<{ id: string }>, r
|
|
|
126
127
|
error: String(err),
|
|
127
128
|
});
|
|
128
129
|
});
|
|
129
|
-
res.json({ triggered:
|
|
130
|
+
res.json({ triggered: taskId, chatSessionId });
|
|
130
131
|
return;
|
|
131
132
|
}
|
|
132
133
|
// Not a user task — check system/skill tasks
|
|
133
134
|
const systemTasks = getSchedulerTasks();
|
|
134
|
-
const found = systemTasks.find((
|
|
135
|
+
const found = systemTasks.find((task) => task.id === taskId);
|
|
135
136
|
if (!found) {
|
|
136
|
-
notFound(res, `task not found: ${
|
|
137
|
+
notFound(res, `task not found: ${taskId}`);
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
140
|
// System tasks don't have a prompt to startChat with — return 400
|
|
@@ -150,11 +151,12 @@ interface LogQuery {
|
|
|
150
151
|
|
|
151
152
|
router.get(API_ROUTES.scheduler.logs, async (req: Request<object, unknown, object, LogQuery>, res: Response<{ logs: TaskLogEntry[] }>) => {
|
|
152
153
|
const MAX_LIMIT = 500;
|
|
153
|
-
const
|
|
154
|
+
const rawLimitStr = getOptionalStringQuery(req, "limit");
|
|
155
|
+
const rawLimit = rawLimitStr ? parseInt(rawLimitStr, 10) : undefined;
|
|
154
156
|
const limit = Number.isFinite(rawLimit) && rawLimit! > 0 ? Math.min(rawLimit!, MAX_LIMIT) : undefined;
|
|
155
157
|
const logs = await getSchedulerLogs({
|
|
156
|
-
since:
|
|
157
|
-
taskId:
|
|
158
|
+
since: getOptionalStringQuery(req, "since"),
|
|
159
|
+
taskId: getOptionalStringQuery(req, "taskId"),
|
|
158
160
|
limit,
|
|
159
161
|
});
|
|
160
162
|
res.json({ logs });
|