mulmoclaude 0.6.0 → 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-BbgSjFQ8.js → index-BwrlMMHr.js} +178 -141
- 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 +9 -7
- package/server/accounting/eventPublisher.ts +2 -1
- package/server/accounting/snapshotCache.ts +2 -1
- package/server/agent/activeTools.ts +16 -6
- 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/mcp-server.ts +9 -6
- package/server/agent/mcp-tools/index.ts +15 -2
- package/server/agent/mcp-tools/notify.ts +20 -2
- 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 +86 -68
- 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/photo-locations.ts +79 -0
- package/server/api/routes/plugins.ts +11 -0
- 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 +130 -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 +138 -16
- package/server/utils/asyncHandler.ts +75 -0
- package/server/utils/exif.ts +321 -0
- package/server/utils/files/accounting-io.ts +19 -20
- package/server/utils/files/attachment-store.ts +69 -12
- 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 +16 -0
- package/server/workspace/photo-locations/index.ts +149 -0
- package/server/workspace/photo-locations/list.ts +124 -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 +78 -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/NewsView.vue +2 -1
- package/src/components/RolesView.vue +14 -5
- package/src/components/SettingsMapTab.vue +140 -0
- package/src/components/SettingsMcpTab.vue +15 -10
- package/src/components/SettingsModal.vue +138 -112
- package/src/components/SettingsModelTab.vue +121 -0
- package/src/components/SettingsPhotosTab.vue +118 -0
- package/src/components/SourcesManager.vue +4 -3
- package/src/components/StackView.vue +43 -12
- package/src/composables/useContentDisplay.ts +16 -0
- package/src/composables/useFileDropZone.ts +148 -0
- package/src/composables/useImageErrorRepair.ts +29 -19
- package/src/composables/useSkillsList.ts +2 -1
- package/src/config/apiRoutes.ts +24 -0
- package/src/config/roles.ts +121 -70
- package/src/config/systemFileDescriptors.ts +2 -2
- package/src/config/toolNames.ts +26 -0
- package/src/index.css +26 -0
- package/src/lang/de.ts +70 -1
- package/src/lang/en.ts +69 -1
- package/src/lang/es.ts +69 -1
- package/src/lang/fr.ts +69 -1
- package/src/lang/ja.ts +69 -1
- package/src/lang/ko.ts +68 -1
- package/src/lang/pt-BR.ts +69 -1
- package/src/lang/zh.ts +67 -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 +8 -0
- package/src/plugins/_extras.ts +6 -2
- package/src/plugins/_generated/metas.ts +4 -0
- package/src/plugins/_generated/registrations.ts +4 -0
- package/src/plugins/_generated/server-bindings.ts +6 -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/manageSkills/View.vue +12 -4
- package/src/plugins/markdown/View.vue +6 -0
- package/src/plugins/photoLocations/View.vue +231 -0
- package/src/plugins/photoLocations/definition.ts +47 -0
- package/src/plugins/photoLocations/index.ts +38 -0
- package/src/plugins/photoLocations/meta.ts +35 -0
- package/src/plugins/presentMulmoScript/View.vue +76 -7
- package/src/plugins/presentMulmoScript/helpers.ts +15 -0
- 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 +15 -16
- package/src/plugins/spreadsheet/View.vue +4 -0
- 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/dom/iframeHeightClamp.ts +42 -0
- package/src/utils/format/bytes.ts +41 -0
- package/src/utils/format/date.ts +14 -2
- package/src/utils/image/imageRepairInlineScript.ts +192 -41
- package/src/utils/markdown/sanitize.ts +68 -0
- package/src/utils/markdown/setup.ts +36 -0
- package/src/utils/markdown/wikiEmbedHandlers.ts +170 -0
- package/src/utils/markdown/wikiEmbeds.ts +141 -0
- package/src/utils/markdown/workspaceLinkify.ts +73 -0
- package/src/utils/path/workspaceLinkRouter.ts +17 -1
- package/client/assets/index-ECD0lgIv.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
|
@@ -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;
|
|
@@ -36,6 +36,7 @@ import { badRequest, conflict, sendError, serverError } from "../../utils/httpEr
|
|
|
36
36
|
import { errorMessage } from "../../utils/errors.js";
|
|
37
37
|
import { API_ROUTES } from "../../../src/config/apiRoutes.js";
|
|
38
38
|
import { bindRoute } from "../../utils/router.js";
|
|
39
|
+
import { asyncHandler } from "../../utils/asyncHandler.js";
|
|
39
40
|
import { isNonEmptyString, isRecord } from "../../utils/types.js";
|
|
40
41
|
|
|
41
42
|
const router = Router();
|
|
@@ -56,15 +57,14 @@ interface ErrorResponse {
|
|
|
56
57
|
error: string;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
bindRoute(
|
|
60
|
-
|
|
60
|
+
bindRoute(
|
|
61
|
+
router,
|
|
62
|
+
API_ROUTES.sources.list,
|
|
63
|
+
asyncHandler<Request, Response<ListSourcesResponse | ErrorResponse>>("sources", "failed to list sources", async (_req, res) => {
|
|
61
64
|
const sources = await listSources(workspacePath);
|
|
62
65
|
res.json({ sources });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
serverError(res, errorMessage(err, "unknown error"));
|
|
66
|
-
}
|
|
67
|
-
});
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
68
|
|
|
69
69
|
// --- POST /api/sources --------------------------------------------------
|
|
70
70
|
|
|
@@ -164,13 +164,15 @@ interface RebuildBody {
|
|
|
164
164
|
scheduleType?: unknown;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
bindRoute(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
bindRoute(
|
|
168
|
+
router,
|
|
169
|
+
API_ROUTES.sources.rebuild,
|
|
170
|
+
asyncHandler<Request<object, unknown, RebuildBody>, Response<ErrorResponse | Record<string, unknown>>>("sources", "rebuild failed", async (req, res) => {
|
|
171
|
+
const scheduleType = validateSchedule(req.body?.scheduleType, "daily");
|
|
172
|
+
if (!scheduleType) {
|
|
173
|
+
badRequest(res, `scheduleType must be one of: ${[...SOURCE_SCHEDULES].join(", ")}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
174
176
|
log.info("sources", "manual rebuild triggered", { scheduleType });
|
|
175
177
|
const result = await runSourcesPipeline({
|
|
176
178
|
workspaceRoot: workspacePath,
|
|
@@ -196,11 +198,8 @@ bindRoute(router, API_ROUTES.sources.rebuild, async (req: Request<object, unknow
|
|
|
196
198
|
archiveErrors: result.archiveErrors,
|
|
197
199
|
isoDate: result.isoDate,
|
|
198
200
|
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
serverError(res, errorMessage(err, "rebuild failed"));
|
|
202
|
-
}
|
|
203
|
-
});
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
204
203
|
|
|
205
204
|
// --- POST /api/sources/manage -------------------------------------------
|
|
206
205
|
//
|
|
@@ -244,31 +243,34 @@ interface ManageSourceSuccess {
|
|
|
244
243
|
|
|
245
244
|
const MANAGE_ACTIONS = new Set(["list", "register", "remove", "rebuild"]);
|
|
246
245
|
|
|
247
|
-
bindRoute(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return;
|
|
258
|
-
case "register":
|
|
259
|
-
await handleRegister(req.body, res);
|
|
260
|
-
return;
|
|
261
|
-
case "remove":
|
|
262
|
-
await handleRemove(req.body, res);
|
|
246
|
+
bindRoute(
|
|
247
|
+
router,
|
|
248
|
+
API_ROUTES.sources.manage,
|
|
249
|
+
asyncHandler<Request<object, unknown, ManageSourceBody>, Response<ManageSourceSuccess | ErrorResponse>>(
|
|
250
|
+
"sources",
|
|
251
|
+
"manageSource dispatch failed",
|
|
252
|
+
async (req, res) => {
|
|
253
|
+
const action = req.body?.action;
|
|
254
|
+
if (typeof action !== "string" || !MANAGE_ACTIONS.has(action)) {
|
|
255
|
+
badRequest(res, `action must be one of: ${[...MANAGE_ACTIONS].join(", ")}`);
|
|
263
256
|
return;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
}
|
|
258
|
+
switch (action) {
|
|
259
|
+
case "list":
|
|
260
|
+
await respondWithList(res, "Loaded source registry.");
|
|
261
|
+
return;
|
|
262
|
+
case "register":
|
|
263
|
+
await handleRegister(req.body, res);
|
|
264
|
+
return;
|
|
265
|
+
case "remove":
|
|
266
|
+
await handleRemove(req.body, res);
|
|
267
|
+
return;
|
|
268
|
+
case "rebuild":
|
|
269
|
+
await handleRebuild(res);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
),
|
|
273
|
+
);
|
|
272
274
|
|
|
273
275
|
async function respondWithList(res: Response<ManageSourceSuccess | ErrorResponse>, message: string, extra: Partial<ManageSourceData> = {}): Promise<void> {
|
|
274
276
|
const sources = await listSources(workspacePath);
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
// itself, so undo stays cheap).
|
|
8
8
|
//
|
|
9
9
|
// Path safety: both `:slug` and `:stamp` are validated *before*
|
|
10
|
-
// they are joined with the workspace root. The slug check
|
|
11
|
-
// `wiki-
|
|
12
|
-
// `FILENAME_RE` shape exposed via `isSafeStamp`.
|
|
10
|
+
// they are joined with the workspace root. The slug check is the
|
|
11
|
+
// shared `isSafeSlug` from `src/lib/wiki-page/slug.ts`; the stamp
|
|
12
|
+
// check is the `FILENAME_RE` shape exposed via `isSafeStamp`.
|
|
13
13
|
|
|
14
14
|
import { Router, type Request, type Response } from "express";
|
|
15
15
|
import path from "node:path";
|
|
@@ -24,20 +24,11 @@ import { readTextOrNull } from "../../../utils/files/safe.js";
|
|
|
24
24
|
import { workspacePath } from "../../../workspace/workspace.js";
|
|
25
25
|
import { pushToolResult } from "../../../events/session-store/index.js";
|
|
26
26
|
import { log } from "../../../system/logger/index.js";
|
|
27
|
+
import { errorMessage } from "../../../utils/errors.js";
|
|
28
|
+
import { isSafeSlug } from "../../../../src/lib/wiki-page/slug.js";
|
|
27
29
|
|
|
28
30
|
const router = Router();
|
|
29
31
|
|
|
30
|
-
// Mirrors `isSafeSlug` from wiki-pages/io.ts (kept independent so
|
|
31
|
-
// the route layer doesn't import the helper through a circular
|
|
32
|
-
// dependency — io.ts already imports snapshot.ts).
|
|
33
|
-
function isSafeSlug(slug: string): boolean {
|
|
34
|
-
if (slug.length === 0) return false;
|
|
35
|
-
if (slug === "." || slug === "..") return false;
|
|
36
|
-
if (slug.includes("/") || slug.includes("\\")) return false;
|
|
37
|
-
if (slug.includes("\0")) return false;
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
32
|
// Restore is a write under the user's workspace; record a short
|
|
42
33
|
// reason on the new snapshot so the history reads "Restored from
|
|
43
34
|
// 2026-04-28T01-23-45-789Z" rather than an empty cell. Editor stays
|
|
@@ -250,7 +241,7 @@ router.post("/internal/snapshot", async (req: Request<object, unknown, InternalS
|
|
|
250
241
|
} catch (err) {
|
|
251
242
|
log.warn("wiki", "page-edit toolResult publish failed", {
|
|
252
243
|
slug,
|
|
253
|
-
error:
|
|
244
|
+
error: errorMessage(err),
|
|
254
245
|
});
|
|
255
246
|
}
|
|
256
247
|
}
|