mulmoclaude 0.6.1 → 0.6.2
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/PluginScopedRoot-YjvQq0Nn.js +3 -0
- package/client/assets/{html2canvas-CDGcmOD3-BbPeutDg.js → html2canvas-CDGcmOD3-Bkf2uOth.js} +1 -1
- package/client/assets/{index-BZdOOa5E.js → index-BwrlMMHr.js} +66 -65
- package/client/assets/index-CvvNuegU.css +2 -0
- package/client/assets/{index.es-DqtpmBm8-DJdTPdnc.js → index.es-DqtpmBm8-D9mAh_KQ.js} +1 -1
- package/client/assets/material-symbols-outlined-BOZVWuR3.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-C1To4M3t.js +1 -0
- package/client/index.html +7 -6
- package/package.json +7 -7
- package/server/accounting/eventPublisher.ts +2 -1
- package/server/accounting/snapshotCache.ts +2 -1
- package/server/agent/backend/claude-code.ts +1 -0
- package/server/agent/backend/types.ts +3 -0
- package/server/agent/config.ts +25 -2
- package/server/agent/index.ts +6 -0
- package/server/agent/prompt.ts +37 -24
- package/server/api/routes/accounting.ts +31 -24
- package/server/api/routes/agent.ts +2 -2
- package/server/api/routes/config-refresh.ts +49 -0
- package/server/api/routes/config.ts +77 -67
- package/server/api/routes/files.ts +41 -17
- package/server/api/routes/hookLog.ts +95 -0
- package/server/api/routes/news.ts +39 -52
- package/server/api/routes/notifier.ts +14 -19
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/presentSvg.ts +107 -0
- package/server/api/routes/scheduler.ts +100 -98
- package/server/api/routes/schedulerTasks.ts +98 -95
- package/server/api/routes/sessions.ts +22 -27
- package/server/api/routes/sources.ts +45 -43
- package/server/api/routes/wiki/history.ts +6 -15
- package/server/api/routes/wiki.ts +73 -276
- package/server/events/file-change.ts +3 -2
- package/server/events/session-store/index.ts +2 -1
- package/server/index.ts +117 -8
- package/server/notifier/store.ts +3 -3
- package/server/plugins/preset-list.ts +16 -5
- package/server/plugins/runtime.ts +2 -2
- package/server/system/config.ts +44 -2
- package/server/utils/asyncHandler.ts +75 -0
- package/server/utils/files/accounting-io.ts +19 -20
- package/server/utils/files/journal-io.ts +2 -1
- package/server/utils/files/json.ts +8 -1
- package/server/utils/files/reference-dirs-io.ts +2 -3
- package/server/utils/files/scheduler-overrides-io.ts +2 -3
- package/server/utils/files/svg-store.ts +27 -0
- package/server/utils/files/user-tasks-io.ts +2 -3
- package/server/utils/regex.ts +3 -12
- package/server/utils/text.ts +29 -0
- package/server/workspace/chat-index/summarizer.ts +5 -3
- package/server/workspace/cooking-recipes/migrate.ts +125 -0
- package/server/workspace/custom-dirs.ts +2 -2
- package/server/workspace/hooks/dispatcher.mjs +300 -0
- package/server/workspace/hooks/dispatcher.ts +55 -0
- package/server/workspace/hooks/handlers/configRefresh.ts +38 -0
- package/server/workspace/hooks/handlers/skillBridge.ts +223 -0
- package/server/workspace/hooks/handlers/wikiSnapshot.ts +43 -0
- package/server/workspace/hooks/provision.ts +222 -0
- package/server/workspace/hooks/shared/sidecar.ts +124 -0
- package/server/workspace/hooks/shared/stdin.ts +60 -0
- package/server/workspace/hooks/shared/workspace.ts +13 -0
- package/server/workspace/journal/dailyPass.ts +1 -6
- package/server/workspace/memory/io.ts +1 -34
- package/server/workspace/memory/migrate.ts +2 -1
- package/server/workspace/memory/snapshot.ts +26 -0
- package/server/workspace/memory/topic-io.ts +1 -18
- package/server/workspace/paths.ts +10 -0
- package/server/workspace/skills-preset/mc-cooking-coach/SKILL.md +217 -0
- package/server/workspace/skills-preset/mc-manage-automations/SKILL.md +119 -0
- package/server/workspace/skills-preset/mc-manage-skills/SKILL.md +128 -0
- package/server/workspace/skills-preset/mc-manage-sources/SKILL.md +106 -0
- package/server/workspace/skills-preset.ts +2 -1
- package/server/workspace/wiki-pages/io.ts +2 -1
- package/src/App.vue +51 -3
- package/src/components/ChatInput.vue +7 -8
- package/src/components/FileContentHeader.vue +1 -6
- package/src/components/FileDropOverlay.vue +18 -0
- package/src/components/RolesView.vue +14 -5
- package/src/components/SettingsMcpTab.vue +15 -10
- package/src/components/SettingsModal.vue +116 -130
- package/src/components/SettingsModelTab.vue +121 -0
- package/src/composables/useContentDisplay.ts +16 -0
- package/src/composables/useFileDropZone.ts +148 -0
- package/src/composables/useSkillsList.ts +2 -1
- package/src/config/apiRoutes.ts +22 -0
- package/src/config/roles.ts +78 -48
- package/src/config/toolNames.ts +4 -1
- package/src/lang/de.ts +36 -1
- package/src/lang/en.ts +36 -1
- package/src/lang/es.ts +36 -1
- package/src/lang/fr.ts +36 -1
- package/src/lang/ja.ts +36 -1
- package/src/lang/ko.ts +36 -1
- package/src/lang/pt-BR.ts +36 -1
- package/src/lang/zh.ts +36 -1
- package/src/lib/wiki-page/index-parse.ts +221 -0
- package/src/lib/wiki-page/link.ts +62 -0
- package/src/lib/wiki-page/lint.ts +105 -0
- package/src/lib/wiki-page/paths.ts +35 -0
- package/src/lib/wiki-page/slug.ts +28 -40
- package/src/main.ts +1 -0
- package/src/plugins/_generated/metas.ts +2 -0
- package/src/plugins/_generated/registrations.ts +2 -0
- package/src/plugins/_generated/server-bindings.ts +3 -0
- package/src/plugins/accounting/Preview.vue +3 -6
- package/src/plugins/accounting/View.vue +2 -1
- package/src/plugins/accounting/components/AccountsModal.vue +3 -2
- package/src/plugins/accounting/components/JournalEntryForm.vue +2 -1
- package/src/plugins/accounting/components/JournalList.vue +2 -1
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +2 -1
- package/src/plugins/accounting/currencies.ts +13 -0
- package/src/plugins/manageRoles/View.vue +16 -5
- package/src/plugins/photoLocations/View.vue +4 -2
- package/src/plugins/presentSVG/Preview.vue +56 -0
- package/src/plugins/presentSVG/View.vue +465 -0
- package/src/plugins/presentSVG/definition.ts +29 -0
- package/src/plugins/presentSVG/index.ts +49 -0
- package/src/plugins/presentSVG/meta.ts +14 -0
- package/src/plugins/scheduler/View.vue +3 -7
- package/src/plugins/skill/View.vue +11 -13
- package/src/plugins/wiki/View.vue +1 -1
- package/src/plugins/wiki/helpers.ts +23 -5
- package/src/plugins/wiki/route.ts +12 -11
- package/src/tools/runtimeLoader.ts +75 -9
- package/src/utils/format/bytes.ts +41 -0
- package/src/utils/format/date.ts +14 -2
- package/src/utils/markdown/setup.ts +5 -0
- package/src/utils/markdown/workspaceLinkify.ts +73 -0
- package/client/assets/index-Bl3vqgA6.css +0 -2
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +0 -1
- package/server/workspace/wiki-history/hook/snapshot.mjs +0 -98
- package/server/workspace/wiki-history/hook/snapshot.ts +0 -135
- package/server/workspace/wiki-history/provision.ts +0 -181
- /package/client/assets/{chunk-D8eiyYIV-C1eAZMzz.js → chunk-D8eiyYIV-CAXpUwLd.js} +0 -0
- /package/client/assets/{purify.es-Fx1Nqyry-BSVNht6S.js → purify.es-Fx1Nqyry-Dwtk-9WZ.js} +0 -0
- /package/client/assets/{typeof-DBp4T-Ny-C2xoZtcz.js → typeof-DBp4T-Ny-CSr8wx1e.js} +0 -0
- /package/client/assets/{vue-1e_vz2LW.js → vue-C8UuIO9J.js} +0 -0
|
@@ -10,8 +10,8 @@ import { saveUserTasks } from "../../utils/files/user-tasks-io.js";
|
|
|
10
10
|
import { startChat } from "./agent.js";
|
|
11
11
|
import { log } from "../../system/logger/index.js";
|
|
12
12
|
import { SCHEDULER_ACTIONS, TASK_ACTIONS } from "../../../src/plugins/scheduler/actions.js";
|
|
13
|
-
import { badRequest, notFound
|
|
14
|
-
import {
|
|
13
|
+
import { badRequest, notFound } from "../../utils/httpError.js";
|
|
14
|
+
import { asyncHandler } from "../../utils/asyncHandler.js";
|
|
15
15
|
import { makeUuid } from "../../utils/id.js";
|
|
16
16
|
|
|
17
17
|
const router = Router();
|
|
@@ -47,118 +47,120 @@ interface SchedulerBody extends SchedulerActionInput {
|
|
|
47
47
|
bindRoute(
|
|
48
48
|
router,
|
|
49
49
|
API_ROUTES.scheduler.dispatch,
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
asyncHandler<Request<object, unknown, SchedulerBody>, Response<DispatchSuccessResponse<ScheduledItem> | DispatchErrorResponse | unknown>>(
|
|
51
|
+
"scheduler",
|
|
52
|
+
"Internal server error",
|
|
53
|
+
async (req, res) => {
|
|
54
|
+
const { action, ...input } = req.body;
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// Route task actions to the user-task subsystem
|
|
57
|
+
if (TASK_ACTIONS.has(action)) {
|
|
58
|
+
await handleTaskAction(action, input, res);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
// Calendar item actions (existing behavior)
|
|
63
|
+
const items = loadItems();
|
|
64
|
+
const result = dispatchScheduler(action, items, input);
|
|
65
|
+
respondWithDispatchResult(res, result, {
|
|
66
|
+
shouldPersist: action !== SCHEDULER_ACTIONS.show,
|
|
67
|
+
instructions: "Display the updated scheduler to the user.",
|
|
68
|
+
persist: saveItems,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
),
|
|
68
72
|
);
|
|
69
73
|
|
|
70
74
|
async function handleTaskAction(action: string, input: Record<string, unknown>, res: Response): Promise<void> {
|
|
71
75
|
log.info("scheduler", "task action: start", { action });
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
}
|
|
76
|
+
// Errors bubble up to the asyncHandler wrapper on the dispatch route,
|
|
77
|
+
// which logs at `log.error("scheduler", "handler threw", …)` and
|
|
78
|
+
// returns a generic 500. No inner try/catch needed here.
|
|
79
|
+
if (action === SCHEDULER_ACTIONS.listTasks) {
|
|
80
|
+
const tasks = loadUserTasks();
|
|
81
|
+
log.info("scheduler", "task action: listTasks ok", { tasks: tasks.length });
|
|
82
|
+
res.json({
|
|
83
|
+
uuid: makeUuid(),
|
|
84
|
+
message: `${tasks.length} scheduled task(s) found.`,
|
|
85
|
+
data: { tasks },
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const tasks = loadUserTasks();
|
|
92
|
-
tasks.push(result.task);
|
|
93
|
-
await saveUserTasks(tasks);
|
|
94
|
-
await refreshUserTasks();
|
|
95
|
-
log.info("scheduler", "task action: createTask ok", { id: result.task.id, name: result.task.name });
|
|
96
|
-
res.json({
|
|
97
|
-
uuid: makeUuid(),
|
|
98
|
-
message: `Task "${result.task.name}" created and scheduled.`,
|
|
99
|
-
data: { task: result.task },
|
|
100
|
-
});
|
|
90
|
+
if (action === SCHEDULER_ACTIONS.createTask) {
|
|
91
|
+
const result = validateAndCreate(input);
|
|
92
|
+
if (result.kind === "error") {
|
|
93
|
+
log.warn("scheduler", "task action: createTask validation failed", { error: result.error });
|
|
94
|
+
badRequest(res, result.error);
|
|
101
95
|
return;
|
|
102
96
|
}
|
|
97
|
+
const tasks = loadUserTasks();
|
|
98
|
+
tasks.push(result.task);
|
|
99
|
+
await saveUserTasks(tasks);
|
|
100
|
+
await refreshUserTasks();
|
|
101
|
+
log.info("scheduler", "task action: createTask ok", { id: result.task.id, name: result.task.name });
|
|
102
|
+
res.json({
|
|
103
|
+
uuid: makeUuid(),
|
|
104
|
+
message: `Task "${result.task.name}" created and scheduled.`,
|
|
105
|
+
data: { task: result.task },
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const { name } = tasks[idx];
|
|
114
|
-
tasks.splice(idx, 1);
|
|
115
|
-
await saveUserTasks(tasks);
|
|
116
|
-
await refreshUserTasks();
|
|
117
|
-
log.info("scheduler", "task action: deleteTask ok", { taskId, name });
|
|
118
|
-
res.json({
|
|
119
|
-
uuid: makeUuid(),
|
|
120
|
-
message: `Task "${name}" deleted.`,
|
|
121
|
-
data: { deleted: taskId },
|
|
122
|
-
});
|
|
110
|
+
if (action === SCHEDULER_ACTIONS.deleteTask) {
|
|
111
|
+
const taskId = typeof input.id === "string" ? input.id : "";
|
|
112
|
+
const tasks = loadUserTasks();
|
|
113
|
+
const idx = tasks.findIndex((task) => task.id === taskId);
|
|
114
|
+
if (idx === -1) {
|
|
115
|
+
log.warn("scheduler", "task action: deleteTask not found", { taskId });
|
|
116
|
+
notFound(res, `task not found: ${taskId}`);
|
|
123
117
|
return;
|
|
124
118
|
}
|
|
119
|
+
const { name } = tasks[idx];
|
|
120
|
+
tasks.splice(idx, 1);
|
|
121
|
+
await saveUserTasks(tasks);
|
|
122
|
+
await refreshUserTasks();
|
|
123
|
+
log.info("scheduler", "task action: deleteTask ok", { taskId, name });
|
|
124
|
+
res.json({
|
|
125
|
+
uuid: makeUuid(),
|
|
126
|
+
message: `Task "${name}" deleted.`,
|
|
127
|
+
data: { deleted: taskId },
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const chatSessionId = makeUuid();
|
|
135
|
-
log.info("scheduler", "manual run via MCP", {
|
|
136
|
-
name: task.name,
|
|
137
|
-
chatSessionId,
|
|
138
|
-
});
|
|
139
|
-
startChat({
|
|
140
|
-
message: task.prompt,
|
|
141
|
-
roleId: task.roleId,
|
|
142
|
-
chatSessionId,
|
|
143
|
-
origin: SESSION_ORIGINS.scheduler,
|
|
144
|
-
}).catch((err) => {
|
|
145
|
-
log.error("scheduler", "manual run failed", {
|
|
146
|
-
error: String(err),
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
res.json({
|
|
150
|
-
uuid: makeUuid(),
|
|
151
|
-
message: `Task "${task.name}" triggered.`,
|
|
152
|
-
data: { triggered: taskId, chatSessionId },
|
|
153
|
-
});
|
|
132
|
+
if (action === SCHEDULER_ACTIONS.runTask) {
|
|
133
|
+
const taskId = typeof input.id === "string" ? input.id : "";
|
|
134
|
+
const tasks = loadUserTasks();
|
|
135
|
+
const task = tasks.find((candidate) => candidate.id === taskId);
|
|
136
|
+
if (!task) {
|
|
137
|
+
notFound(res, `task not found: ${taskId}`);
|
|
154
138
|
return;
|
|
155
139
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
140
|
+
const chatSessionId = makeUuid();
|
|
141
|
+
log.info("scheduler", "manual run via MCP", {
|
|
142
|
+
name: task.name,
|
|
143
|
+
chatSessionId,
|
|
144
|
+
});
|
|
145
|
+
startChat({
|
|
146
|
+
message: task.prompt,
|
|
147
|
+
roleId: task.roleId,
|
|
148
|
+
chatSessionId,
|
|
149
|
+
origin: SESSION_ORIGINS.scheduler,
|
|
150
|
+
}).catch((err) => {
|
|
151
|
+
log.error("scheduler", "manual run failed", {
|
|
152
|
+
error: String(err),
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
res.json({
|
|
156
|
+
uuid: makeUuid(),
|
|
157
|
+
message: `Task "${task.name}" triggered.`,
|
|
158
|
+
data: { triggered: taskId, chatSessionId },
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
badRequest(res, `unknown task action: ${action}`);
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
export default router;
|
|
@@ -14,20 +14,23 @@ import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
|
14
14
|
import { bindRoute } from "../../utils/router.js";
|
|
15
15
|
import { SESSION_ORIGINS } from "../../../src/types/session.js";
|
|
16
16
|
import { loadUserTasks, validateAndCreate, applyUpdate, withUserTaskLock } from "../../workspace/skills/user-tasks.js";
|
|
17
|
-
import { badRequest, notFound
|
|
17
|
+
import { badRequest, notFound } from "../../utils/httpError.js";
|
|
18
18
|
import { errorMessage } from "../../utils/errors.js";
|
|
19
19
|
import { getOptionalStringQuery } from "../../utils/request.js";
|
|
20
20
|
import { log } from "../../system/logger/index.js";
|
|
21
21
|
import { startChat } from "./agent.js";
|
|
22
22
|
import { makeUuid } from "../../utils/id.js";
|
|
23
|
+
import { asyncHandler } from "../../utils/asyncHandler.js";
|
|
23
24
|
|
|
24
25
|
const router = Router();
|
|
25
26
|
|
|
26
27
|
// ── List all tasks ──────────────────────────────────────────────
|
|
27
28
|
|
|
28
|
-
bindRoute(
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
bindRoute(
|
|
30
|
+
router,
|
|
31
|
+
API_ROUTES.scheduler.tasksList,
|
|
32
|
+
asyncHandler("scheduler-tasks", "Failed to list tasks", async (_req, res) => {
|
|
33
|
+
log.info("scheduler-tasks", "list: start");
|
|
31
34
|
// getSchedulerTasks() returns system-only tasks (registered via
|
|
32
35
|
// initScheduler at startup — journal, chat-index, sources, etc.).
|
|
33
36
|
// origin: "system" is correct, not an overwrite — these tasks
|
|
@@ -37,95 +40,92 @@ bindRoute(router, API_ROUTES.scheduler.tasksList, (_req: Request, res: Response)
|
|
|
37
40
|
const all = [...systemTasks.map((task) => ({ ...task, origin: "system" as const })), ...userTasks.map((task) => ({ ...task, origin: "user" as const }))];
|
|
38
41
|
log.info("scheduler-tasks", "list: ok", { system: systemTasks.length, user: userTasks.length });
|
|
39
42
|
res.json({ tasks: all });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// queries the in-memory registry — neither is supposed to
|
|
43
|
-
// throw, but a corrupted user-tasks.json or an early-startup
|
|
44
|
-
// registry race would. Without the catch the route would 500
|
|
45
|
-
// with no trace, which is the original #779 complaint pattern.
|
|
46
|
-
log.error("scheduler-tasks", "list: failed", { error: errorMessage(err) });
|
|
47
|
-
serverError(res, "Failed to list tasks");
|
|
48
|
-
}
|
|
49
|
-
});
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
50
45
|
|
|
51
46
|
// ── Create user task ────────────────────────────────────────────
|
|
52
47
|
|
|
53
|
-
bindRoute(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
log.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
bindRoute(
|
|
49
|
+
router,
|
|
50
|
+
API_ROUTES.scheduler.tasksCreate,
|
|
51
|
+
asyncHandler("scheduler-tasks", "Failed to create task", async (req, res) => {
|
|
52
|
+
log.info("scheduler-tasks", "create: start");
|
|
53
|
+
const validated = validateAndCreate(req.body);
|
|
54
|
+
if (validated.kind === "error") {
|
|
55
|
+
log.warn("scheduler-tasks", "create: validation failed", { error: validated.error });
|
|
56
|
+
badRequest(res, validated.error);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
62
59
|
const task = await withUserTaskLock(async (tasks) => ({
|
|
63
60
|
tasks: [...tasks, validated.task],
|
|
64
61
|
result: validated.task,
|
|
65
62
|
}));
|
|
66
63
|
log.info("scheduler-tasks", "create: ok", { id: task.id, name: task.name });
|
|
67
64
|
res.status(201).json({ task });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
error: String(err),
|
|
71
|
-
});
|
|
72
|
-
serverError(res, "Failed to create task");
|
|
73
|
-
}
|
|
74
|
-
});
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
75
67
|
|
|
76
68
|
// ── Update user task ────────────────────────────────────────────
|
|
77
69
|
|
|
78
|
-
bindRoute(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
70
|
+
bindRoute(
|
|
71
|
+
router,
|
|
72
|
+
API_ROUTES.scheduler.taskUpdate,
|
|
73
|
+
asyncHandler<Request<{ id: string }>, Response>("scheduler-tasks", "Failed to update task", async (req, res) => {
|
|
74
|
+
const { id: taskId } = req.params;
|
|
75
|
+
log.info("scheduler-tasks", "update: start", { taskId });
|
|
76
|
+
try {
|
|
77
|
+
const updated = await withUserTaskLock(async (tasks) => {
|
|
78
|
+
const result = applyUpdate(tasks, taskId, req.body);
|
|
79
|
+
if (result.kind === "error") {
|
|
80
|
+
throw new Error(result.error);
|
|
81
|
+
}
|
|
82
|
+
const task = result.tasks.find((taskItem) => taskItem.id === taskId);
|
|
83
|
+
return { tasks: result.tasks, result: task };
|
|
84
|
+
});
|
|
85
|
+
log.info("scheduler-tasks", "update: ok", { taskId });
|
|
86
|
+
res.json({ task: updated });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
// Domain-shaped errors → 404; everything else rethrows for the
|
|
89
|
+
// asyncHandler wrapper to surface as 500.
|
|
90
|
+
const msg = errorMessage(err);
|
|
91
|
+
if (msg.startsWith("task not found") || msg.startsWith("request body")) {
|
|
92
|
+
log.warn("scheduler-tasks", "update: validation failed", { taskId, reason: msg });
|
|
93
|
+
notFound(res, msg);
|
|
94
|
+
return;
|
|
86
95
|
}
|
|
87
|
-
|
|
88
|
-
return { tasks: result.tasks, result: task };
|
|
89
|
-
});
|
|
90
|
-
log.info("scheduler-tasks", "update: ok", { taskId });
|
|
91
|
-
res.json({ task: updated });
|
|
92
|
-
} catch (err) {
|
|
93
|
-
const msg = errorMessage(err);
|
|
94
|
-
if (msg.startsWith("task not found") || msg.startsWith("request body")) {
|
|
95
|
-
log.warn("scheduler-tasks", "update: validation failed", { taskId, reason: msg });
|
|
96
|
-
notFound(res, msg);
|
|
97
|
-
return;
|
|
96
|
+
throw err;
|
|
98
97
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
});
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
103
100
|
|
|
104
101
|
// ── Delete user task ────────────────────────────────────────────
|
|
105
102
|
|
|
106
|
-
bindRoute(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
103
|
+
bindRoute(
|
|
104
|
+
router,
|
|
105
|
+
API_ROUTES.scheduler.taskDelete,
|
|
106
|
+
asyncHandler<Request<{ id: string }>, Response>("scheduler-tasks", "Failed to delete task", async (req, res) => {
|
|
107
|
+
const { id: taskId } = req.params;
|
|
108
|
+
log.info("scheduler-tasks", "delete: start", { taskId });
|
|
109
|
+
try {
|
|
110
|
+
await withUserTaskLock(async (tasks) => {
|
|
111
|
+
const index = tasks.findIndex((task) => task.id === taskId);
|
|
112
|
+
if (index === -1) throw new Error(`task not found: ${taskId}`);
|
|
113
|
+
const next = tasks.filter((task) => task.id !== taskId);
|
|
114
|
+
return { tasks: next, result: undefined };
|
|
115
|
+
});
|
|
116
|
+
log.info("scheduler-tasks", "delete: ok", { taskId });
|
|
117
|
+
res.json({ deleted: taskId });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const msg = errorMessage(err);
|
|
120
|
+
if (msg.startsWith("task not found")) {
|
|
121
|
+
log.warn("scheduler-tasks", "delete: not found", { taskId });
|
|
122
|
+
notFound(res, msg);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
124
126
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
});
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
129
|
|
|
130
130
|
// ── Manual trigger ──────────────────────────────────────────────
|
|
131
131
|
|
|
@@ -177,25 +177,28 @@ interface LogQuery {
|
|
|
177
177
|
limit?: string;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
bindRoute(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
180
|
+
bindRoute(
|
|
181
|
+
router,
|
|
182
|
+
API_ROUTES.scheduler.logs,
|
|
183
|
+
asyncHandler<Request<object, unknown, object, LogQuery>, Response<{ logs: TaskLogEntry[] }>>(
|
|
184
|
+
"scheduler-tasks",
|
|
185
|
+
"Failed to read scheduler logs",
|
|
186
|
+
async (req, res) => {
|
|
187
|
+
const MAX_LIMIT = 500;
|
|
188
|
+
const rawLimitStr = getOptionalStringQuery(req, "limit");
|
|
189
|
+
const rawLimit = rawLimitStr ? parseInt(rawLimitStr, 10) : undefined;
|
|
190
|
+
const limit = rawLimit !== undefined && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, MAX_LIMIT) : undefined;
|
|
191
|
+
const taskId = getOptionalStringQuery(req, "taskId");
|
|
192
|
+
log.info("scheduler-tasks", "logs: start", { taskId, limit });
|
|
193
|
+
const logs = await getSchedulerLogs({
|
|
194
|
+
since: getOptionalStringQuery(req, "since"),
|
|
195
|
+
taskId,
|
|
196
|
+
limit,
|
|
197
|
+
});
|
|
198
|
+
log.info("scheduler-tasks", "logs: ok", { entries: logs.length, taskId });
|
|
199
|
+
res.json({ logs });
|
|
200
|
+
},
|
|
201
|
+
),
|
|
202
|
+
);
|
|
200
203
|
|
|
201
204
|
export default router;
|
|
@@ -25,6 +25,7 @@ import { ONE_DAY_MS } from "../../utils/time.js";
|
|
|
25
25
|
import { encodeCursor, parseCursor, sessionChangeMs } from "./sessionsCursor.js";
|
|
26
26
|
import { errorMessage } from "../../utils/errors.js";
|
|
27
27
|
import { log } from "../../system/logger/index.js";
|
|
28
|
+
import { asyncHandler } from "../../utils/asyncHandler.js";
|
|
28
29
|
|
|
29
30
|
interface SessionMeta {
|
|
30
31
|
roleId: string;
|
|
@@ -355,14 +356,13 @@ router.post(API_ROUTES.sessions.markRead, async (req: Request<SessionIdParams>,
|
|
|
355
356
|
// Toggle the user-set bookmark flag on a session's meta sidecar.
|
|
356
357
|
router.post(
|
|
357
358
|
API_ROUTES.sessions.bookmark,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
try {
|
|
359
|
+
asyncHandler<Request<SessionIdParams, { ok: boolean } | SessionErrorResponse, { bookmarked: boolean }>, Response<{ ok: boolean } | SessionErrorResponse>>(
|
|
360
|
+
"sessions",
|
|
361
|
+
"Failed to update bookmark",
|
|
362
|
+
async (req, res) => {
|
|
363
|
+
const { id: sessionId } = req.params;
|
|
364
|
+
const bookmarked = Boolean(req.body?.bookmarked);
|
|
365
|
+
log.info("sessions", "bookmark: start", { sessionId, bookmarked });
|
|
366
366
|
await updateIsBookmarked(sessionId, bookmarked);
|
|
367
367
|
// Meta-mtime bumps on the write — cursor diff will pick up the
|
|
368
368
|
// change on the next refetch — but every other tab also needs
|
|
@@ -370,11 +370,8 @@ router.post(
|
|
|
370
370
|
publishSessionsChanged();
|
|
371
371
|
log.info("sessions", "bookmark: ok", { sessionId, bookmarked });
|
|
372
372
|
res.json({ ok: true });
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
res.status(500).json({ error: "Failed to update bookmark" });
|
|
376
|
-
}
|
|
377
|
-
},
|
|
373
|
+
},
|
|
374
|
+
),
|
|
378
375
|
);
|
|
379
376
|
|
|
380
377
|
// Hard-delete a session: remove the jsonl, meta sidecar, AND the
|
|
@@ -395,24 +392,22 @@ router.post(
|
|
|
395
392
|
// 3. Only after disk is clean do we evict from the store and fire
|
|
396
393
|
// `notifySessionsChanged({ deletedIds })`. Now the broadcast is
|
|
397
394
|
// a truthful statement.
|
|
398
|
-
router.delete(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
log.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
395
|
+
router.delete(
|
|
396
|
+
API_ROUTES.sessions.detail,
|
|
397
|
+
asyncHandler<Request<SessionIdParams>, Response<{ ok: boolean } | SessionErrorResponse>>("sessions", "Failed to delete session", async (req, res) => {
|
|
398
|
+
const { id: sessionId } = req.params;
|
|
399
|
+
log.info("sessions", "delete: start", { sessionId });
|
|
400
|
+
if (getSession(sessionId)?.isRunning) {
|
|
401
|
+
log.warn("sessions", "delete: refused — session running", { sessionId });
|
|
402
|
+
res.status(409).json({ error: "Session is running. Cancel the run before deleting." });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
407
405
|
await deleteSessionFiles(sessionId);
|
|
408
406
|
await removeSessionFromIndex(workspacePath, sessionId);
|
|
409
407
|
evictSession(sessionId);
|
|
410
408
|
log.info("sessions", "delete: ok", { sessionId });
|
|
411
409
|
res.json({ ok: true });
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
res.status(500).json({ error: "Failed to delete session" });
|
|
415
|
-
}
|
|
416
|
-
});
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
417
412
|
|
|
418
413
|
export default router;
|