mulmoclaude 0.9.2 → 0.9.4

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 (67) hide show
  1. package/bin/mulmoclaude.js +2 -1
  2. package/client/assets/{index-Dc0R-HW5.js → index-B3NxFcEH.js} +46 -46
  3. package/client/assets/{index-Dxo1Zdd-.css → index-BXb--as6.css} +1 -1
  4. package/client/assets/{marp-CSq0PPfK.js → marp-C9QDHFAJ.js} +1 -1
  5. package/client/index.html +2 -2
  6. package/package.json +11 -10
  7. package/server/api/routes/agent.ts +5 -1
  8. package/server/api/routes/chat-index.ts +5 -1
  9. package/server/api/routes/collections.ts +152 -1
  10. package/server/api/routes/config.ts +12 -0
  11. package/server/index.ts +14 -2
  12. package/server/prompts/system/system.md +7 -1
  13. package/server/remoteHost/firebase.ts +6 -0
  14. package/server/remoteHost/handlers/collectionPage.ts +15 -17
  15. package/server/remoteHost/handlers/getCollection.ts +2 -2
  16. package/server/remoteHost/handlers/getFeed.ts +2 -2
  17. package/server/remoteHost/handlers/getRemoteView.ts +37 -0
  18. package/server/remoteHost/handlers/getRemoteViewItems.ts +38 -0
  19. package/server/remoteHost/handlers/index.ts +10 -0
  20. package/server/remoteHost/handlers/ingestAttachments.ts +88 -0
  21. package/server/remoteHost/handlers/listAccountingBooks.ts +31 -0
  22. package/server/remoteHost/handlers/listCollections.ts +6 -1
  23. package/server/remoteHost/handlers/listSkills.ts +39 -0
  24. package/server/remoteHost/handlers/mutateRemoteView.ts +40 -0
  25. package/server/remoteHost/handlers/startChat.ts +103 -39
  26. package/server/system/config.ts +101 -4
  27. package/server/system/logs/aaa +737 -0
  28. package/server/system/logs/bb +446 -0
  29. package/server/utils/files/thumbnail-store.ts +97 -0
  30. package/server/workspace/chat-index/index.ts +29 -3
  31. package/server/workspace/chat-index/indexer.ts +187 -29
  32. package/server/workspace/chat-index/summarizer.ts +18 -10
  33. package/server/workspace/collections/index.ts +2 -1
  34. package/server/workspace/collections/remoteView.ts +290 -0
  35. package/server/workspace/journal/archivist-cli.ts +17 -3
  36. package/server/workspace/journal/dailyPass.ts +52 -2
  37. package/server/workspace/journal/index.ts +20 -4
  38. package/src/App.vue +62 -3
  39. package/src/components/FileTree.vue +21 -1
  40. package/src/components/FileTreePane.vue +41 -25
  41. package/src/components/FilesView.vue +4 -0
  42. package/src/components/RemoteHostControl.vue +18 -0
  43. package/src/components/SessionHistoryPanel.vue +24 -11
  44. package/src/components/SettingsChatIndexTab.vue +119 -0
  45. package/src/components/SettingsJournalTab.vue +117 -0
  46. package/src/components/SettingsModal.vue +12 -2
  47. package/src/composables/collections/uiHost.ts +15 -0
  48. package/src/composables/collections/useDynamicShortcutIcons.ts +93 -0
  49. package/src/composables/usePubSub.ts +40 -2
  50. package/src/composables/useSessionSync.ts +9 -0
  51. package/src/composables/useShowHiddenSystemFiles.ts +23 -0
  52. package/src/config/apiRoutes.ts +20 -0
  53. package/src/config/roles.ts +2 -2
  54. package/src/config/visibleWorkspaceDirs.ts +21 -0
  55. package/src/lang/de.ts +47 -0
  56. package/src/lang/en.ts +45 -0
  57. package/src/lang/es.ts +46 -0
  58. package/src/lang/fr.ts +47 -0
  59. package/src/lang/ja.ts +46 -0
  60. package/src/lang/ko.ts +45 -0
  61. package/src/lang/pt-BR.ts +46 -0
  62. package/src/lang/zh.ts +44 -0
  63. package/src/plugins/textResponse/View.vue +49 -14
  64. package/src/plugins/textResponse/utils.ts +37 -0
  65. package/src/utils/html/previewCsp.ts +7 -16
  66. package/src/utils/role/icon.ts +9 -2
  67. package/src/utils/session/sessionPreview.ts +29 -0
@@ -23,6 +23,27 @@ import { isRecord, isStringArray, isStringRecord } from "../utils/types.js";
23
23
  export const EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"] as const;
24
24
  export type EffortLevel = (typeof EFFORT_LEVELS)[number];
25
25
 
26
+ // Chat-index summarizer setting (#1944). "off" disables the whole
27
+ // AI-title / summary / keywords background indexer; "haiku" / "sonnet"
28
+ // select the Claude model the summarizer spawns. Default (undefined)
29
+ // is treated as "off" — indexing is opt-in from Settings → Chat index
30
+ // so a fresh workspace doesn't burn CLI budget on scheduler/system
31
+ // automation sessions the user may never look at (see #1929 / #1944
32
+ // for the cost analysis). The origin filter (system + scheduler
33
+ // sessions always skipped) is enforced separately and does NOT
34
+ // depend on this setting.
35
+ export const CHAT_INDEX_MODES = ["off", "haiku", "sonnet"] as const;
36
+ export type ChatIndexMode = (typeof CHAT_INDEX_MODES)[number];
37
+
38
+ // Journal daily-pass setting (follow-up to #1944). Same three states as
39
+ // chat-index: "off" disables both the turn-end hook and the hourly
40
+ // scheduled pass; "haiku" / "sonnet" pick the Claude model the archivist
41
+ // CLI spawns for its summary calls. Default (undefined) is "off" so a
42
+ // fresh workspace doesn't burn CLI budget until the user opts in from
43
+ // Settings → Journal.
44
+ export const JOURNAL_MODES = ["off", "haiku", "sonnet"] as const;
45
+ export type JournalMode = (typeof JOURNAL_MODES)[number];
46
+
26
47
  export interface AppSettings {
27
48
  // Extra tool names appended to BASE_ALLOWED_TOOLS in
28
49
  // server/agent/config.ts#buildCliArgs. Typical entries are
@@ -63,6 +84,24 @@ export interface AppSettings {
63
84
  enabled: boolean;
64
85
  model?: string;
65
86
  };
87
+
88
+ // Chat-index summarizer mode (#1944). Ships "off" by default: the
89
+ // background AI-title / summary / keywords indexer stays disabled
90
+ // until the user opts in from Settings → Chat index. Flipping to
91
+ // "haiku" or "sonnet" selects the model the summarizer spawns per
92
+ // session. Non-user origins (`system`, `scheduler`) are always
93
+ // skipped regardless of this value — see `indexer.ts`.
94
+ chatIndex?: ChatIndexMode;
95
+
96
+ // Journal daily-pass mode (follow-up to #1944). Ships "off" by
97
+ // default: the archivist that summarizes chat sessions into
98
+ // journal/*.md stays disabled until the user opts in from Settings →
99
+ // Journal. Flipping to "haiku" / "sonnet" enables the run and picks
100
+ // the Claude model the archivist CLI spawns. The turn-end hook
101
+ // (`maybeRunJournal` in the agent finally block) and the hourly
102
+ // `system:journal` scheduled task both honour this — off short-
103
+ // circuits before the interval gate is even consulted.
104
+ journal?: JournalMode;
66
105
  }
67
106
 
68
107
  const DEFAULT_SETTINGS: AppSettings = { extraAllowedTools: [] };
@@ -94,6 +133,14 @@ function isEffortLevel(value: unknown): value is EffortLevel {
94
133
  return typeof value === "string" && (EFFORT_LEVELS as readonly string[]).includes(value);
95
134
  }
96
135
 
136
+ function isChatIndexMode(value: unknown): value is ChatIndexMode {
137
+ return typeof value === "string" && (CHAT_INDEX_MODES as readonly string[]).includes(value);
138
+ }
139
+
140
+ function isJournalMode(value: unknown): value is JournalMode {
141
+ return typeof value === "string" && (JOURNAL_MODES as readonly string[]).includes(value);
142
+ }
143
+
97
144
  function isVoiceInputSettings(value: unknown): value is { enabled: boolean; model?: string } {
98
145
  if (!isRecord(value)) return false;
99
146
  if (typeof value.enabled !== "boolean") return false;
@@ -108,9 +155,13 @@ export function isAppSettings(value: unknown): value is AppSettings {
108
155
  if (value.photoExif !== undefined && !isPhotoExifSettings(value.photoExif)) return false;
109
156
  if (value.effortLevel !== undefined && !isEffortLevel(value.effortLevel)) return false;
110
157
  if (value.voiceInput !== undefined && !isVoiceInputSettings(value.voiceInput)) return false;
158
+ if (value.chatIndex !== undefined && !isChatIndexMode(value.chatIndex)) return false;
159
+ if (value.journal !== undefined && !isJournalMode(value.journal)) return false;
111
160
  return true;
112
161
  }
113
162
 
163
+ // isAppSettingsPatch adds a null sentinel to `chatIndex` / `journal` (see below).
164
+
114
165
  /** A PUT-payload validator: every field optional, but if present
115
166
  * it must match the AppSettings shape. Distinct from
116
167
  * `isAppSettings` (which insists on the full storage shape) so a
@@ -127,28 +178,46 @@ export function isAppSettings(value: unknown): value is AppSettings {
127
178
  * but lets nullable fields carry `null` as a "clear me" sentinel —
128
179
  * callers normalise via `normaliseAppSettingsPatch` before merging
129
180
  * into the storage shape (#1323). */
130
- export type AppSettingsPatch = Partial<Omit<AppSettings, "effortLevel">> & {
181
+ export type AppSettingsPatch = Partial<Omit<AppSettings, "effortLevel" | "chatIndex" | "journal">> & {
131
182
  effortLevel?: EffortLevel | null;
183
+ chatIndex?: ChatIndexMode | null;
184
+ journal?: JournalMode | null;
132
185
  };
133
186
 
134
187
  /** Convert a wire patch to the storage-shape patch by dropping any
135
188
  * `null` sentinels (which mean "clear" for the corresponding field). */
136
189
  export function normaliseAppSettingsPatch(patch: AppSettingsPatch): Partial<AppSettings> {
137
- const { effortLevel, ...rest } = patch;
190
+ const { effortLevel, chatIndex, journal, ...rest } = patch;
138
191
  const out: Partial<AppSettings> = { ...rest };
139
192
  if (effortLevel !== null && effortLevel !== undefined) {
140
193
  out.effortLevel = effortLevel;
141
194
  }
195
+ if (chatIndex !== null && chatIndex !== undefined) {
196
+ out.chatIndex = chatIndex;
197
+ }
198
+ if (journal !== null && journal !== undefined) {
199
+ out.journal = journal;
200
+ }
142
201
  return out;
143
202
  }
144
203
 
204
+ // Split each field's optional-with-null-sentinel validation into its own
205
+ // mini-helper so `isAppSettingsPatch` stays under the cognitive-complexity
206
+ // ceiling as new nullable fields land.
207
+ const isOptionalString = (value: unknown): boolean => value === undefined || typeof value === "string";
208
+ const isOptionalNullableEffortLevel = (value: unknown): boolean => value === undefined || value === null || isEffortLevel(value);
209
+ const isOptionalNullableChatIndexMode = (value: unknown): boolean => value === undefined || value === null || isChatIndexMode(value);
210
+ const isOptionalNullableJournalMode = (value: unknown): boolean => value === undefined || value === null || isJournalMode(value);
211
+
145
212
  export function isAppSettingsPatch(value: unknown): value is AppSettingsPatch {
146
213
  if (!isRecord(value)) return false;
147
214
  if (value.extraAllowedTools !== undefined && !isStringArray(value.extraAllowedTools)) return false;
148
- if (value.googleMapsApiKey !== undefined && typeof value.googleMapsApiKey !== "string") return false;
215
+ if (!isOptionalString(value.googleMapsApiKey)) return false;
149
216
  if (value.photoExif !== undefined && !isPhotoExifSettings(value.photoExif)) return false;
150
- if (value.effortLevel !== undefined && value.effortLevel !== null && !isEffortLevel(value.effortLevel)) return false;
217
+ if (!isOptionalNullableEffortLevel(value.effortLevel)) return false;
151
218
  if (value.voiceInput !== undefined && !isVoiceInputSettings(value.voiceInput)) return false;
219
+ if (!isOptionalNullableChatIndexMode(value.chatIndex)) return false;
220
+ if (!isOptionalNullableJournalMode(value.journal)) return false;
152
221
  return true;
153
222
  }
154
223
 
@@ -182,9 +251,31 @@ function cloneAppSettings(settings: AppSettings): AppSettings {
182
251
  copy.voiceInput.model = settings.voiceInput.model;
183
252
  }
184
253
  }
254
+ if (settings.chatIndex !== undefined) {
255
+ copy.chatIndex = settings.chatIndex;
256
+ }
257
+ if (settings.journal !== undefined) {
258
+ copy.journal = settings.journal;
259
+ }
185
260
  return copy;
186
261
  }
187
262
 
263
+ /** Chat-index mode with the documented "undefined → off" default, so
264
+ * every reader (indexer, backfill scheduler, force-startup switch,
265
+ * Settings tab) resolves the same way. Callers switch on the
266
+ * returned literal string to decide skip vs pick a model. */
267
+ export function chatIndexMode(settings: AppSettings): ChatIndexMode {
268
+ return settings.chatIndex ?? "off";
269
+ }
270
+
271
+ /** Journal mode with the documented "undefined → off" default, so
272
+ * every reader (turn-end hook, hourly scheduler, force-startup
273
+ * switch, Settings tab) resolves the same way. Callers switch on
274
+ * the returned literal string to decide skip vs pick a model. */
275
+ export function journalMode(settings: AppSettings): JournalMode {
276
+ return settings.journal ?? "off";
277
+ }
278
+
188
279
  /** Read the photo-exif auto-capture flag with the documented
189
280
  * default of `true`. Centralises the "missing block ⇒ on" rule so
190
281
  * the post-save hook + future Settings UI stay aligned. */
@@ -234,6 +325,12 @@ export function saveSettings(settings: AppSettings): void {
234
325
  payload.voiceInput.model = settings.voiceInput.model;
235
326
  }
236
327
  }
328
+ if (settings.chatIndex !== undefined) {
329
+ payload.chatIndex = settings.chatIndex;
330
+ }
331
+ if (settings.journal !== undefined) {
332
+ payload.journal = settings.journal;
333
+ }
237
334
  const serialised = JSON.stringify(payload, null, 2);
238
335
  writeFileAtomicSync(settingsPath(), `${serialised}\n`, { mode: 0o600 });
239
336
  }