mulmoclaude 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/bin/mulmoclaude.js +1 -1
  2. package/client/assets/{index-KNLBjwuh.css → index-Bm70FDU2.css} +1 -1
  3. package/client/assets/{index-D8rhwXLq.js → index-eHWB79u5.js} +3 -3
  4. package/client/index.html +2 -2
  5. package/package.json +1 -1
  6. package/server/agent/config.ts +12 -12
  7. package/server/agent/mcp-server.ts +19 -19
  8. package/server/agent/mcp-tools/x.ts +5 -5
  9. package/server/agent/prompt.ts +9 -4
  10. package/server/agent/sandboxMounts.ts +7 -7
  11. package/server/agent/stream.ts +4 -4
  12. package/server/api/routes/files.ts +9 -9
  13. package/server/api/routes/scheduler.ts +8 -8
  14. package/server/api/routes/schedulerHandlers.ts +12 -12
  15. package/server/api/routes/schedulerTasks.ts +14 -14
  16. package/server/api/routes/sessions.ts +24 -24
  17. package/server/api/routes/todosColumnsHandlers.ts +30 -30
  18. package/server/api/routes/wiki.ts +14 -14
  19. package/server/events/scheduler-adapter.ts +20 -20
  20. package/server/events/session-store/index.ts +10 -10
  21. package/server/events/task-manager/index.ts +7 -7
  22. package/server/index.ts +19 -19
  23. package/server/utils/date.ts +18 -18
  24. package/server/utils/files/atomic.ts +9 -9
  25. package/server/utils/files/html-io.ts +5 -5
  26. package/server/utils/files/image-store.ts +2 -2
  27. package/server/utils/files/journal-io.ts +2 -2
  28. package/server/utils/files/naming.ts +2 -2
  29. package/server/utils/files/roles-io.ts +10 -10
  30. package/server/utils/files/scheduler-io.ts +5 -5
  31. package/server/utils/files/session-io.ts +35 -35
  32. package/server/utils/files/spreadsheet-store.ts +2 -2
  33. package/server/utils/files/todos-io.ts +9 -9
  34. package/server/utils/files/user-tasks-io.ts +5 -5
  35. package/server/workspace/chat-index/indexer.ts +15 -15
  36. package/server/workspace/custom-dirs.ts +11 -11
  37. package/server/workspace/journal/archivist.ts +35 -35
  38. package/server/workspace/journal/dailyPass.ts +31 -28
  39. package/server/workspace/journal/indexFile.ts +29 -25
  40. package/server/workspace/reference-dirs.ts +18 -18
  41. package/server/workspace/roles.ts +6 -6
  42. package/server/workspace/skills/discovery.ts +4 -4
  43. package/server/workspace/skills/user-tasks.ts +34 -34
  44. package/server/workspace/sources/arxivDiscovery.ts +8 -8
  45. package/server/workspace/sources/classifier.ts +7 -7
  46. package/server/workspace/sources/fetchers/arxiv.ts +7 -7
  47. package/server/workspace/sources/fetchers/githubIssues.ts +7 -7
  48. package/server/workspace/sources/fetchers/githubReleases.ts +7 -7
  49. package/server/workspace/sources/interests.ts +9 -9
  50. package/server/workspace/sources/pipeline/index.ts +6 -6
  51. package/server/workspace/sources/pipeline/plan.ts +5 -5
  52. package/server/workspace/sources/registry.ts +16 -16
  53. package/server/workspace/sources/robots.ts +14 -14
  54. package/server/workspace/sources/sourceState.ts +11 -9
  55. package/server/workspace/tool-trace/index.ts +1 -1
  56. package/server/workspace/tool-trace/writeSearch.ts +26 -16
  57. package/server/workspace/wiki-backlinks/index.ts +8 -8
  58. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +15 -15
  59. package/src/App.vue +30 -30
  60. package/src/components/ChatInput.vue +7 -7
  61. package/src/components/LockStatusPopup.vue +2 -2
  62. package/src/components/NotificationToast.vue +2 -2
  63. package/src/components/RoleSelector.vue +2 -2
  64. package/src/components/SessionHistoryPanel.vue +6 -6
  65. package/src/components/SettingsMcpTab.vue +7 -7
  66. package/src/components/SettingsModal.vue +3 -3
  67. package/src/components/SettingsReferenceDirsTab.vue +10 -10
  68. package/src/components/SettingsWorkspaceDirsTab.vue +5 -5
  69. package/src/components/SuggestionsPanel.vue +2 -2
  70. package/src/components/todo/TodoAddDialog.vue +2 -2
  71. package/src/components/todo/TodoEditPanel.vue +2 -2
  72. package/src/components/todo/TodoListView.vue +5 -5
  73. package/src/composables/useCanvasViewMode.ts +5 -5
  74. package/src/composables/useClickOutside.ts +2 -2
  75. package/src/composables/useFreshPluginData.ts +3 -3
  76. package/src/composables/useKeyNavigation.ts +11 -11
  77. package/src/composables/useMcpTools.ts +2 -2
  78. package/src/composables/useNotifications.ts +3 -3
  79. package/src/composables/usePdfDownload.ts +4 -4
  80. package/src/composables/usePendingCalls.ts +1 -1
  81. package/src/composables/usePubSub.ts +10 -10
  82. package/src/composables/useRoles.ts +1 -1
  83. package/src/composables/useSandboxStatus.ts +1 -1
  84. package/src/composables/useSessionDerived.ts +3 -3
  85. package/src/composables/useSessionSync.ts +8 -8
  86. package/src/composables/useViewLayout.ts +2 -2
  87. package/src/config/roles.ts +2 -2
  88. package/src/plugins/chart/Preview.vue +4 -4
  89. package/src/plugins/manageSkills/View.vue +3 -3
  90. package/src/plugins/manageSource/Preview.vue +1 -1
  91. package/src/plugins/markdown/View.vue +2 -2
  92. package/src/plugins/presentHtml/helpers.ts +8 -8
  93. package/src/plugins/presentMulmoScript/View.vue +4 -4
  94. package/src/plugins/presentMulmoScript/helpers.ts +1 -1
  95. package/src/plugins/scheduler/Preview.vue +6 -6
  96. package/src/plugins/scheduler/TasksTab.vue +4 -4
  97. package/src/plugins/textResponse/View.vue +2 -2
  98. package/src/plugins/todo/Preview.vue +2 -2
  99. package/src/plugins/todo/View.vue +11 -11
  100. package/src/plugins/todo/composables/useTodos.ts +5 -5
  101. package/src/plugins/wiki/Preview.vue +5 -5
  102. package/src/plugins/wiki/helpers.ts +4 -4
  103. package/src/router/guards.ts +12 -12
  104. package/src/types/session.ts +4 -3
  105. package/src/utils/agent/request.ts +3 -3
  106. package/src/utils/dom/scrollable.ts +2 -2
  107. package/src/utils/files/expandedDirs.ts +1 -1
  108. package/src/utils/files/sortChildren.ts +6 -6
  109. package/src/utils/format/frontmatter.ts +6 -6
  110. package/src/utils/image/rewriteMarkdownImageRefs.ts +5 -5
  111. package/src/utils/markdown/extractFirstH1.ts +2 -2
  112. package/src/utils/path/relativeLink.ts +15 -15
  113. package/src/utils/role/icon.ts +2 -2
  114. package/src/utils/role/merge.ts +2 -2
  115. package/src/utils/role/plugins.ts +1 -1
  116. package/src/utils/session/sessionFactory.ts +2 -2
  117. package/src/utils/session/sessionHelpers.ts +2 -2
  118. package/src/utils/tools/dedup.ts +4 -4
  119. package/src/utils/tools/result.ts +3 -3
  120. package/src/utils/types.ts +2 -2
@@ -59,7 +59,7 @@ export function usePendingCalls(opts: UsePendingCallsOptions) {
59
59
  // unused.
60
60
  const __tickDep = displayTick.value;
61
61
  const now = Date.now();
62
- return opts.toolCallHistory.value.filter((c) => __tickDep >= 0 && isCallStillPending(c, now));
62
+ return opts.toolCallHistory.value.filter((entry) => __tickDep >= 0 && isCallStillPending(entry, now));
63
63
  });
64
64
 
65
65
  function teardown(): void {
@@ -21,16 +21,16 @@ let socket: Socket | null = null;
21
21
 
22
22
  const listeners = new Map<string, Set<Callback>>();
23
23
 
24
- function resendSubscriptions(s: Socket): void {
24
+ function resendSubscriptions(sock: Socket): void {
25
25
  for (const channel of listeners.keys()) {
26
- s.emit("subscribe", channel);
26
+ sock.emit("subscribe", channel);
27
27
  }
28
28
  }
29
29
 
30
30
  function connect(): Socket {
31
31
  if (socket) return socket;
32
32
 
33
- const s = io({
33
+ const sock = io({
34
34
  path: "/ws/pubsub",
35
35
  // Match the server. Long-polling is fine as a fallback but
36
36
  // the server refuses it, so don't negotiate it here either —
@@ -38,17 +38,17 @@ function connect(): Socket {
38
38
  transports: ["websocket"],
39
39
  });
40
40
 
41
- s.on("connect", () => resendSubscriptions(s));
41
+ sock.on("connect", () => resendSubscriptions(sock));
42
42
 
43
- s.on("data", (msg: PubSubMessage) => {
43
+ sock.on("data", (msg: PubSubMessage) => {
44
44
  const cbs = listeners.get(msg.channel);
45
45
  if (cbs) {
46
- for (const cb of cbs) cb(msg.data);
46
+ for (const handler of cbs) handler(msg.data);
47
47
  }
48
48
  });
49
49
 
50
- socket = s;
51
- return s;
50
+ socket = sock;
51
+ return sock;
52
52
  }
53
53
 
54
54
  function maybeDisconnect(): void {
@@ -63,8 +63,8 @@ export function usePubSub() {
63
63
  if (!listeners.has(channel)) listeners.set(channel, new Set());
64
64
  listeners.get(channel)!.add(callback);
65
65
 
66
- const s = connect();
67
- if (s.connected) s.emit("subscribe", channel);
66
+ const sock = connect();
67
+ if (sock.connected) sock.emit("subscribe", channel);
68
68
  // If not yet connected, the "connect" handler replays every
69
69
  // listener's subscription, so newly-added channels are
70
70
  // covered without extra bookkeeping.
@@ -17,7 +17,7 @@ export function useRoles(): {
17
17
  } {
18
18
  const roles = ref<Role[]>(ROLES);
19
19
  const currentRoleId = ref(ROLES[0].id);
20
- const currentRole = computed(() => roles.value.find((r) => r.id === currentRoleId.value) ?? roles.value[0]);
20
+ const currentRole = computed(() => roles.value.find((role) => role.id === currentRoleId.value) ?? roles.value[0]);
21
21
 
22
22
  async function refreshRoles(): Promise<void> {
23
23
  const result = await apiGet<Role[]>(API_ROUTES.roles.list);
@@ -29,7 +29,7 @@ function isSandboxStatus(raw: RawResponse): raw is {
29
29
  } {
30
30
  if (typeof raw.sshAgent !== "boolean") return false;
31
31
  if (!Array.isArray(raw.mounts)) return false;
32
- return raw.mounts.every((m) => typeof m === "string");
32
+ return raw.mounts.every((mount) => typeof mount === "string");
33
33
  }
34
34
 
35
35
  export interface UseSandboxStatusHandle {
@@ -16,7 +16,7 @@ export function useSessionDerived(opts: { sessionMap: Map<string, ActiveSession>
16
16
 
17
17
  const sidebarResults = computed(() => deduplicateResults(toolResults.value));
18
18
 
19
- const currentSummary = computed(() => sessions.value.find((s) => s.id === currentSessionId.value));
19
+ const currentSummary = computed(() => sessions.value.find((summary) => summary.id === currentSessionId.value));
20
20
 
21
21
  // The server-side summary already merges pendingGenerations into
22
22
  // `isRunning` (see server/api/routes/sessions.ts), but pub/sub events
@@ -33,9 +33,9 @@ export function useSessionDerived(opts: { sessionMap: Map<string, ActiveSession>
33
33
 
34
34
  const toolCallHistory = computed<ToolCallHistoryItem[]>(() => activeSession.value?.toolCallHistory ?? []);
35
35
 
36
- const activeSessionCount = computed(() => sessions.value.filter((s) => s.isRunning).length);
36
+ const activeSessionCount = computed(() => sessions.value.filter((session) => session.isRunning).length);
37
37
 
38
- const unreadCount = computed(() => sessions.value.filter((s) => s.hasUnread).length);
38
+ const unreadCount = computed(() => sessions.value.filter((session) => session.hasUnread).length);
39
39
 
40
40
  return {
41
41
  activeSession,
@@ -29,20 +29,20 @@ export function useSessionSync(opts: {
29
29
  console.warn("[session-sync] failed to fetch sessions:", err);
30
30
  return;
31
31
  }
32
- for (const s of summaries) {
33
- const live = sessionMap.get(s.id);
32
+ for (const summary of summaries) {
33
+ const live = sessionMap.get(summary.id);
34
34
  if (!live) continue;
35
- live.isRunning = s.isRunning ?? false;
36
- live.statusMessage = s.statusMessage ?? "";
37
- const unread = s.hasUnread ?? false;
38
- if (!(unread && s.id === currentSessionId.value)) {
35
+ live.isRunning = summary.isRunning ?? false;
36
+ live.statusMessage = summary.statusMessage ?? "";
37
+ const unread = summary.hasUnread ?? false;
38
+ if (!(unread && summary.id === currentSessionId.value)) {
39
39
  live.hasUnread = unread;
40
40
  }
41
41
  }
42
42
  }
43
43
 
44
- async function markSessionRead(id: string): Promise<void> {
45
- const result = await apiPost<{ ok: boolean }>(API_ROUTES.sessions.markRead.replace(":id", encodeURIComponent(id)));
44
+ async function markSessionRead(sessionId: string): Promise<void> {
45
+ const result = await apiPost<{ ok: boolean }>(API_ROUTES.sessions.markRead.replace(":id", encodeURIComponent(sessionId)));
46
46
  if (!result.ok || result.data.ok === false) {
47
47
  await refreshSessionStates();
48
48
  }
@@ -9,8 +9,8 @@ import { CANVAS_VIEW, type CanvasViewMode } from "../utils/canvas/viewMode";
9
9
  const CHAT_VIEWS = [CANVAS_VIEW.single, CANVAS_VIEW.stack] as const;
10
10
  type ChatViewMode = (typeof CHAT_VIEWS)[number];
11
11
 
12
- function isChatView(m: string): m is ChatViewMode {
13
- return (CHAT_VIEWS as readonly string[]).includes(m);
12
+ function isChatView(mode: string): mode is ChatViewMode {
13
+ return (CHAT_VIEWS as readonly string[]).includes(mode);
14
14
  }
15
15
 
16
16
  export function useViewLayout(opts: {
@@ -330,6 +330,6 @@ export type BuiltInRoleId = (typeof BUILTIN_ROLE_IDS)[keyof typeof BUILTIN_ROLE_
330
330
 
331
331
  export const DEFAULT_ROLE_ID: BuiltInRoleId = BUILTIN_ROLE_IDS.general;
332
332
 
333
- export function getRole(id: string): Role {
334
- return ROLES.find((r) => r.id === id) ?? ROLES[0];
333
+ export function getRole(roleId: string): Role {
334
+ return ROLES.find((role) => role.id === roleId) ?? ROLES[0];
335
335
  }
@@ -25,8 +25,8 @@ const hint = computed(() => {
25
25
  const charts = data.value?.document?.charts ?? [];
26
26
  if (charts.length === 0) return "";
27
27
  const types = charts
28
- .map((c) => c.type ?? inferTypeFromOption(c.option))
29
- .filter((t): t is string => Boolean(t))
28
+ .map((chart) => chart.type ?? inferTypeFromOption(chart.option))
29
+ .filter((chartType): chartType is string => Boolean(chartType))
30
30
  .slice(0, 3);
31
31
  const suffix = charts.length > types.length ? ", …" : "";
32
32
  const typeList = types.join(", ");
@@ -41,8 +41,8 @@ function inferTypeFromOption(option: Record<string, unknown>): string | null {
41
41
  const first = series[0] as { type?: unknown };
42
42
  if (typeof first.type === "string") return first.type;
43
43
  } else if (series && typeof series === "object") {
44
- const t = (series as { type?: unknown }).type;
45
- if (typeof t === "string") return t;
44
+ const seriesType = (series as { type?: unknown }).type;
45
+ if (typeof seriesType === "string") return seriesType;
46
46
  }
47
47
  return null;
48
48
  }
@@ -173,7 +173,7 @@ const saving = ref(false);
173
173
  const editDescription = ref("");
174
174
  const editBody = ref("");
175
175
 
176
- const selected = computed(() => skills.value.find((s) => s.name === selectedName.value) ?? null);
176
+ const selected = computed(() => skills.value.find((skill) => skill.name === selectedName.value) ?? null);
177
177
 
178
178
  const renderedBody = computed(() => {
179
179
  const body = detail.value?.body;
@@ -272,7 +272,7 @@ async function saveEdit(): Promise<void> {
272
272
  body: editBody.value,
273
273
  };
274
274
  // Update the sidebar summary too.
275
- const idx = skills.value.findIndex((s) => s.name === name);
275
+ const idx = skills.value.findIndex((skill) => skill.name === name);
276
276
  if (idx >= 0) {
277
277
  skills.value[idx] = {
278
278
  ...skills.value[idx],
@@ -311,7 +311,7 @@ async function deleteSkill(): Promise<void> {
311
311
  return;
312
312
  }
313
313
  // Remove from the local list, advance selection, clear detail.
314
- const idx = skills.value.findIndex((s) => s.name === name);
314
+ const idx = skills.value.findIndex((skill) => skill.name === name);
315
315
  if (idx >= 0) {
316
316
  skills.value.splice(idx, 1);
317
317
  }
@@ -24,7 +24,7 @@ const hint = computed(() => {
24
24
  if (sources.length === 0) return "No sources registered yet.";
25
25
  const names = sources
26
26
  .slice(0, 3)
27
- .map((s: Source) => s.slug)
27
+ .map((source: Source) => source.slug)
28
28
  .join(", ");
29
29
  const tail = sources.length > 3 ? ", …" : "";
30
30
  const plural = sources.length === 1 ? "" : "s";
@@ -156,8 +156,8 @@ const sourceDetails = ref<HTMLDetailsElement>();
156
156
  const editing = ref(false);
157
157
  const { copied, copy } = useClipboardCopy();
158
158
 
159
- function onDetailsToggle(e: Event) {
160
- const open = (e.target as HTMLDetailsElement).open;
159
+ function onDetailsToggle(event: Event) {
160
+ const open = (event.target as HTMLDetailsElement).open;
161
161
  editing.value = open;
162
162
  if (!open) {
163
163
  editableMarkdown.value = markdownContent.value;
@@ -25,8 +25,8 @@ export function stripHtmlToPreview(html: string, maxLength: number): string {
25
25
  };
26
26
  let i = 0;
27
27
  while (i < html.length) {
28
- const c = html[i];
29
- if (c === "<") {
28
+ const char = html[i];
29
+ if (char === "<") {
30
30
  const close = html.indexOf(">", i + 1);
31
31
  if (close !== -1) {
32
32
  // Real tag span `<...>` — skip it, emit a separator.
@@ -36,7 +36,7 @@ export function stripHtmlToPreview(html: string, maxLength: number): string {
36
36
  }
37
37
  // No closing `>` anywhere after — treat as literal.
38
38
  }
39
- emitChar(state, c);
39
+ emitChar(state, char);
40
40
  i++;
41
41
  }
42
42
  trimTrailingSpace(state.out);
@@ -48,12 +48,12 @@ interface WalkerState {
48
48
  lastWasSpace: boolean;
49
49
  }
50
50
 
51
- function emitChar(state: WalkerState, c: string): void {
52
- if (isWhitespace(c)) {
51
+ function emitChar(state: WalkerState, char: string): void {
52
+ if (isWhitespace(char)) {
53
53
  emitSeparator(state);
54
54
  return;
55
55
  }
56
- state.out.push(c);
56
+ state.out.push(char);
57
57
  state.lastWasSpace = false;
58
58
  }
59
59
 
@@ -67,6 +67,6 @@ function trimTrailingSpace(out: string[]): void {
67
67
  if (out.length > 0 && out[out.length - 1] === " ") out.pop();
68
68
  }
69
69
 
70
- function isWhitespace(c: string): boolean {
71
- return c === " " || c === "\t" || c === "\n" || c === "\r" || c === "\v" || c === "\f";
70
+ function isWhitespace(char: string): boolean {
71
+ return char === " " || char === "\t" || char === "\n" || char === "\r" || char === "\v" || char === "\f";
72
72
  }
@@ -431,7 +431,7 @@ const charErrors = reactive<Record<string, string>>({});
431
431
  const charDragOver = reactive<Record<string, boolean>>({});
432
432
  const beatDragOver = reactive<Record<number, boolean>>({});
433
433
 
434
- const anyBeatRendering = computed(() => Object.values(renderState).some((s) => s === "rendering"));
434
+ const anyBeatRendering = computed(() => Object.values(renderState).some((state) => state === "rendering"));
435
435
 
436
436
  const characterKeys = computed(() => {
437
437
  const imgs = script.value.imageParams?.images ?? {};
@@ -447,10 +447,10 @@ const chatSessionId = computed(() => activeSessionRef?.value?.id);
447
447
  const pendingForThisScript = computed(() => {
448
448
  const out: Record<string, PendingGeneration> = {};
449
449
  const pending = activeSessionRef?.value?.pendingGenerations ?? {};
450
- const fp = filePath.value;
451
- if (!fp) return out;
450
+ const currentPath = filePath.value;
451
+ if (!currentPath) return out;
452
452
  for (const [mapKey, entry] of Object.entries(pending)) {
453
- if (entry.filePath === fp) out[mapKey] = entry;
453
+ if (entry.filePath === currentPath) out[mapKey] = entry;
454
454
  }
455
455
  return out;
456
456
  });
@@ -63,7 +63,7 @@ export function shouldAutoRenderBeat(beat: { image?: { type?: string } | undefin
63
63
  * what's missing after a movie-generation event arrives.
64
64
  */
65
65
  export function getMissingCharacterKeys(keys: readonly string[], images: Record<string, unknown>, renderState: Record<string, string | undefined>): string[] {
66
- return keys.filter((k) => !images[k] && renderState[k] !== "rendering");
66
+ return keys.filter((charKey) => !images[charKey] && renderState[charKey] !== "rendering");
67
67
  }
68
68
 
69
69
  /**
@@ -26,8 +26,8 @@ const items = ref<ScheduledItem[]>(props.result.data?.items ?? []);
26
26
  const { refresh } = useFreshPluginData<ScheduledItem[]>({
27
27
  endpoint: () => API_ROUTES.scheduler.base,
28
28
  extract: (json) => {
29
- const v = (json as { data?: { items?: ScheduledItem[] } }).data?.items;
30
- return Array.isArray(v) ? v : null;
29
+ const extracted = (json as { data?: { items?: ScheduledItem[] } }).data?.items;
30
+ return Array.isArray(extracted) ? extracted : null;
31
31
  },
32
32
  apply: (data) => {
33
33
  items.value = data;
@@ -49,15 +49,15 @@ const upcomingItems = computed(() => {
49
49
  const noDate: ScheduledItem[] = [];
50
50
 
51
51
  for (const item of items.value) {
52
- const d = item.props.date;
53
- if (typeof d === "string") {
54
- if (d >= today) withDate.push(item);
52
+ const dateVal = item.props.date;
53
+ if (typeof dateVal === "string") {
54
+ if (dateVal >= today) withDate.push(item);
55
55
  } else {
56
56
  noDate.push(item);
57
57
  }
58
58
  }
59
59
 
60
- withDate.sort((a, b) => (String(a.props.date) < String(b.props.date) ? -1 : 1));
60
+ withDate.sort((itemA, itemB) => (String(itemA.props.date) < String(itemB.props.date) ? -1 : 1));
61
61
 
62
62
  return [...withDate, ...noDate];
63
63
  });
@@ -168,9 +168,9 @@ function formatSchedule(schedule: TaskSchedule): string {
168
168
  return JSON.stringify(schedule);
169
169
  }
170
170
 
171
- async function runTask(id: string): Promise<void> {
171
+ async function runTask(taskId: string): Promise<void> {
172
172
  mutationError.value = "";
173
- const url = API_ROUTES.scheduler.taskRun.replace(":id", id);
173
+ const url = API_ROUTES.scheduler.taskRun.replace(":id", taskId);
174
174
  const result = await apiPost(url, {});
175
175
  if (!result.ok) {
176
176
  mutationError.value = `Run failed: ${result.error}`;
@@ -190,9 +190,9 @@ async function toggleEnabled(task: SchedulerTask): Promise<void> {
190
190
  await fetchTasks();
191
191
  }
192
192
 
193
- async function deleteTask(id: string): Promise<void> {
193
+ async function deleteTask(taskId: string): Promise<void> {
194
194
  mutationError.value = "";
195
- const url = API_ROUTES.scheduler.task.replace(":id", id);
195
+ const url = API_ROUTES.scheduler.task.replace(":id", taskId);
196
196
  const result = await apiDelete(url);
197
197
  if (!result.ok) {
198
198
  mutationError.value = `Delete failed: ${result.error}`;
@@ -168,8 +168,8 @@ const { pdfDownloading, pdfError, downloadPdf: rawDownloadPdf } = usePdfDownload
168
168
  const detailsEl = ref<HTMLDetailsElement>();
169
169
  const editing = ref(false);
170
170
 
171
- function onDetailsToggle(e: Event) {
172
- editing.value = (e.target as HTMLDetailsElement).open;
171
+ function onDetailsToggle(event: Event) {
172
+ editing.value = (event.target as HTMLDetailsElement).open;
173
173
  }
174
174
 
175
175
  onMounted(() => {
@@ -42,8 +42,8 @@ const items = ref<TodoItem[]>(props.result.data?.items ?? []);
42
42
  const { refresh } = useFreshPluginData<TodoItem[]>({
43
43
  endpoint: () => API_ROUTES.todos.list,
44
44
  extract: (json) => {
45
- const v = (json as { data?: { items?: TodoItem[] } }).data?.items;
46
- return Array.isArray(v) ? v : null;
45
+ const extracted = (json as { data?: { items?: TodoItem[] } }).data?.items;
46
+ return Array.isArray(extracted) ? extracted : null;
47
47
  },
48
48
  apply: (data) => {
49
49
  items.value = data;
@@ -111,8 +111,8 @@ const items = ref<TodoItem[]>(props.selectedResult.data?.items ?? []);
111
111
  const { refresh } = useFreshPluginData<TodoItem[]>({
112
112
  endpoint: () => API_ROUTES.todos.list,
113
113
  extract: (json) => {
114
- const v = (json as { data?: { items?: TodoItem[] } }).data?.items;
115
- return Array.isArray(v) ? v : null;
114
+ const extracted = (json as { data?: { items?: TodoItem[] } }).data?.items;
115
+ return Array.isArray(extracted) ? extracted : null;
116
116
  },
117
117
  apply: (data) => {
118
118
  items.value = data;
@@ -163,12 +163,12 @@ function clearFilters(): void {
163
163
 
164
164
  // ── YAML helpers ─────────────────────────────────────────────────────────────
165
165
 
166
- function yamlStringValue(v: string): string {
167
- const needsQuotes = v === "" || /[:#[\]{},&*?|<>=!%@`]/.test(v) || /^\s|\s$/.test(v) || /^(true|false|null|~)$/i.test(v) || /^\d/.test(v);
166
+ function yamlStringValue(str: string): string {
167
+ const needsQuotes = str === "" || /[:#[\]{},&*?|<>=!%@`]/.test(str) || /^\s|\s$/.test(str) || /^(true|false|null|~)$/i.test(str) || /^\d/.test(str);
168
168
  if (needsQuotes) {
169
- return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
169
+ return `"${str.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
170
170
  }
171
- return v;
171
+ return str;
172
172
  }
173
173
 
174
174
  function serializeYaml(item: TodoItem): string {
@@ -191,19 +191,19 @@ function parseFlowSequence(raw: string): string[] {
191
191
  let buffer = "";
192
192
  let inQuotes = false;
193
193
  for (let i = 0; i < inner.length; i++) {
194
- const ch = inner[i];
195
- if (ch === '"' && inner[i - 1] !== "\\") {
194
+ const char = inner[i];
195
+ if (char === '"' && inner[i - 1] !== "\\") {
196
196
  inQuotes = !inQuotes;
197
- buffer += ch;
197
+ buffer += char;
198
198
  continue;
199
199
  }
200
- if (ch === "," && !inQuotes) {
200
+ if (char === "," && !inQuotes) {
201
201
  const piece = parseYamlValue(buffer.trim());
202
202
  if (piece) result.push(piece);
203
203
  buffer = "";
204
204
  continue;
205
205
  }
206
- buffer += ch;
206
+ buffer += char;
207
207
  }
208
208
  const last = parseYamlValue(buffer.trim());
209
209
  if (last) result.push(last);
@@ -166,12 +166,12 @@ export function useTodos(initialItems: TodoItem[] = [], initialColumns: StatusCo
166
166
  error,
167
167
  refresh,
168
168
  createItem: (input) => call(API_ROUTES.todos.items, "POST", input),
169
- patchItem: (id, input) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(id)), "PATCH", input),
170
- moveItem: (id, input) => call(API_ROUTES.todos.itemMove.replace(":id", encodeURIComponent(id)), "POST", input),
171
- deleteItem: (id) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(id)), "DELETE"),
169
+ patchItem: (itemId, input) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(itemId)), "PATCH", input),
170
+ moveItem: (itemId, input) => call(API_ROUTES.todos.itemMove.replace(":id", encodeURIComponent(itemId)), "POST", input),
171
+ deleteItem: (itemId) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(itemId)), "DELETE"),
172
172
  addColumn: (input) => call(API_ROUTES.todos.columns, "POST", input),
173
- patchColumn: (id, input) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(id)), "PATCH", input),
174
- deleteColumn: (id) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(id)), "DELETE"),
173
+ patchColumn: (colId, input) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(colId)), "PATCH", input),
174
+ deleteColumn: (colId) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(colId)), "DELETE"),
175
175
  reorderColumns: (ids) => call(API_ROUTES.todos.columnsOrder, "PUT", { ids }),
176
176
  };
177
177
  }
@@ -43,11 +43,11 @@ const { refresh } = useFreshPluginData<WikiData>({
43
43
  watch(
44
44
  () => props.result.uuid,
45
45
  () => {
46
- const d = props.result.data;
47
- if (d) {
48
- action.value = d.action ?? "index";
49
- title.value = d.title ?? "Wiki";
50
- pageEntries.value = d.pageEntries ?? [];
46
+ const wikiData = props.result.data;
47
+ if (wikiData) {
48
+ action.value = wikiData.action ?? "index";
49
+ title.value = wikiData.title ?? "Wiki";
50
+ pageEntries.value = wikiData.pageEntries ?? [];
51
51
  }
52
52
  void refresh();
53
53
  },
@@ -43,11 +43,11 @@ export function renderWikiLinks(content: string): string {
43
43
  * immediately after `from` (zero-length page name, which the old
44
44
  * regex rejected via the `+` quantifier).
45
45
  */
46
- function findNextCloseBrackets(s: string, from: number): number {
46
+ function findNextCloseBrackets(str: string, from: number): number {
47
47
  let j = from;
48
- while (j < s.length) {
49
- if (s[j] === "]") {
50
- if (s[j + 1] === "]" && j > from) return j;
48
+ while (j < str.length) {
49
+ if (str[j] === "]") {
50
+ if (str[j + 1] === "]" && j > from) return j;
51
51
  // Bare `]` inside the page-name span — old regex would not
52
52
  // match here, so we bail and let the caller emit the `[[`
53
53
  // as literal text.
@@ -18,43 +18,43 @@ import { VALID_VIEW_MODES } from "../utils/canvas/viewMode";
18
18
  // doesn't exist on the server" gracefully.
19
19
  const SESSION_ID_RE = /^[\w-]{1,128}$/;
20
20
 
21
- function isValidSessionId(id: unknown): boolean {
22
- return typeof id === "string" && SESSION_ID_RE.test(id);
21
+ function isValidSessionId(value: unknown): boolean {
22
+ return typeof value === "string" && SESSION_ID_RE.test(value);
23
23
  }
24
24
 
25
25
  export function installGuards(router: Router): void {
26
- router.beforeEach((to) => {
26
+ router.beforeEach((dest) => {
27
27
  // Only run guards on the chat route — other routes (redirect, etc.)
28
28
  // don't carry parameters that need sanitizing.
29
- if (to.name !== "chat") return;
29
+ if (dest.name !== "chat") return;
30
30
 
31
31
  // ── sessionId format check ───────────────────────────────────
32
- const sessionId = to.params.sessionId;
32
+ const sessionId = dest.params.sessionId;
33
33
  if (typeof sessionId === "string" && sessionId.length > 0 && !isValidSessionId(sessionId)) {
34
34
  // Garbage sessionId → strip it and go to /chat (new session).
35
35
  return { name: "chat", params: {}, query: {}, replace: true };
36
36
  }
37
37
 
38
38
  // ── view mode whitelist ──────────────────────────────────────
39
- const view = to.query.view;
39
+ const view = dest.query.view;
40
40
  if (typeof view === "string" && !VALID_VIEW_MODES.has(view)) {
41
- const cleaned = { ...to.query };
41
+ const cleaned = { ...dest.query };
42
42
  delete cleaned.view;
43
- return { ...to, query: cleaned, replace: true };
43
+ return { ...dest, query: cleaned, replace: true };
44
44
  }
45
45
 
46
46
  // ── file path traversal check ────────────────────────────────
47
- const filePath = to.query.path;
47
+ const filePath = dest.query.path;
48
48
  if (typeof filePath === "string") {
49
49
  if (filePath.includes("..") || filePath.startsWith("/")) {
50
- const cleaned = { ...to.query };
50
+ const cleaned = { ...dest.query };
51
51
  delete cleaned.path;
52
- return { ...to, query: cleaned, replace: true };
52
+ return { ...dest, query: cleaned, replace: true };
53
53
  }
54
54
 
55
55
  // ?path= without ?view=files → auto-add view=files so FilesView mounts.
56
56
  if (view !== "files") {
57
- return { ...to, query: { ...to.query, view: "files" }, replace: true };
57
+ return { ...dest, query: { ...dest.query, view: "files" }, replace: true };
58
58
  }
59
59
  }
60
60
  });
@@ -70,10 +70,11 @@ export interface ToolResultEntry extends SessionEntry {
70
70
  result: ToolResultComplete;
71
71
  }
72
72
 
73
- export const isTextEntry = (e: SessionEntry): e is TextEntry =>
74
- (e.source === "user" || e.source === "assistant") && e.type === EVENT_TYPES.text && typeof e.message === "string";
73
+ export const isTextEntry = (entry: SessionEntry): entry is TextEntry =>
74
+ (entry.source === "user" || entry.source === "assistant") && entry.type === EVENT_TYPES.text && typeof entry.message === "string";
75
75
 
76
- export const isToolResultEntry = (e: SessionEntry): e is ToolResultEntry => e.source === "tool" && e.type === EVENT_TYPES.toolResult && e.result !== undefined;
76
+ export const isToolResultEntry = (entry: SessionEntry): entry is ToolResultEntry =>
77
+ entry.source === "tool" && entry.type === EVENT_TYPES.toolResult && entry.result !== undefined;
77
78
 
78
79
  // In-memory session held in `sessionMap`. PR #88 introduced this so
79
80
  // multiple chats can run concurrently — `id` matches the `chatSessionId`
@@ -45,11 +45,11 @@ export async function postAgentRun(body: AgentRequestBody): Promise<{ ok: true }
45
45
  };
46
46
  }
47
47
  return { ok: true };
48
- } catch (e) {
49
- console.error("[agent] fetch error:", e);
48
+ } catch (err) {
49
+ console.error("[agent] fetch error:", err);
50
50
  return {
51
51
  ok: false,
52
- error: e instanceof Error ? e.message : "Connection error.",
52
+ error: err instanceof Error ? err.message : "Connection error.",
53
53
  };
54
54
  }
55
55
  }
@@ -11,8 +11,8 @@
11
11
  // real DOM.
12
12
  export function findScrollableChild(container: HTMLElement): HTMLElement | null {
13
13
  const children = container.querySelectorAll("*");
14
- for (const el of children) {
15
- const html = el as HTMLElement;
14
+ for (const elem of children) {
15
+ const html = elem as HTMLElement;
16
16
  if (html.scrollHeight > html.clientHeight) {
17
17
  const style = getComputedStyle(html);
18
18
  if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") {
@@ -13,7 +13,7 @@ export function parseStoredExpandedDirs(raw: string | null): Set<string> {
13
13
  try {
14
14
  const parsed: unknown = JSON.parse(raw);
15
15
  if (!Array.isArray(parsed)) return new Set(DEFAULT_EXPANDED);
16
- const strings = parsed.filter((v): v is string => typeof v === "string");
16
+ const strings = parsed.filter((val): val is string => typeof val === "string");
17
17
  return new Set(strings);
18
18
  } catch {
19
19
  return new Set(DEFAULT_EXPANDED);
@@ -7,14 +7,14 @@ import type { FileSortMode } from "../../composables/useFileSortMode";
7
7
  // on name so the order is deterministic).
8
8
  export function sortChildren(children: readonly TreeNode[], mode: FileSortMode): TreeNode[] {
9
9
  const copy = children.slice();
10
- copy.sort((a, b) => {
11
- if (a.type !== b.type) return a.type === "dir" ? -1 : 1;
10
+ copy.sort((nodeA, nodeB) => {
11
+ if (nodeA.type !== nodeB.type) return nodeA.type === "dir" ? -1 : 1;
12
12
  if (mode === "recent") {
13
- const am = a.modifiedMs ?? -Infinity;
14
- const bm = b.modifiedMs ?? -Infinity;
15
- if (am !== bm) return bm - am;
13
+ const modTimeA = nodeA.modifiedMs ?? -Infinity;
14
+ const modTimeB = nodeB.modifiedMs ?? -Infinity;
15
+ if (modTimeA !== modTimeB) return modTimeB - modTimeA;
16
16
  }
17
- return a.name.localeCompare(b.name);
17
+ return nodeA.name.localeCompare(nodeB.name);
18
18
  });
19
19
  return copy;
20
20
  }