mulmoclaude 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/client/assets/{index-X_qGlIa9.js → index-tZkbdenJ.js} +405 -377
- package/client/assets/{marp-BEiOVLVW.js → marp-YZRvgIPH.js} +1 -1
- package/client/index.html +1 -1
- package/package.json +6 -6
- package/server/agent/activeTools.ts +9 -5
- package/server/agent/backend/claude-code.ts +3 -0
- package/server/agent/backend/types.ts +3 -0
- package/server/agent/config.ts +11 -1
- package/server/agent/dispatchableMcpTool.ts +19 -0
- package/server/agent/index.ts +2 -1
- package/server/agent/mcp-server.ts +7 -5
- package/server/agent/skillListing.ts +34 -0
- package/server/agent/skillListingState.ts +40 -0
- package/server/api/routes/config.ts +5 -0
- package/server/api/routes/roles.ts +1 -0
- package/server/build/mcp-server.mjs +502 -474
- package/server/prompts/system/memory-management-atomic.md +1 -27
- package/server/prompts/system/memory-management-topic.md +4 -53
- package/server/prompts/system/system.md +2 -5
- package/server/system/config.ts +25 -3
- package/src/components/SettingsModelTab.vue +58 -1
- package/src/config/roles.ts +6 -1
- package/src/lang/de.ts +6 -0
- package/src/lang/en.ts +6 -0
- package/src/lang/es.ts +6 -0
- package/src/lang/fr.ts +6 -0
- package/src/lang/ja.ts +6 -0
- package/src/lang/ko.ts +6 -0
- package/src/lang/pt-BR.ts +6 -0
- package/src/lang/zh.ts +6 -0
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +3 -0
- package/src/plugins/manageRoles/roleForm.ts +8 -2
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
## Memory Management
|
|
2
2
|
|
|
3
|
-
When you learn something from the conversation that would be useful to remember in future sessions, silently save it as a typed entry under `conversations/memory/`. Do not ask permission — just write it.
|
|
4
|
-
|
|
5
|
-
Each entry is one markdown file with YAML frontmatter:
|
|
6
|
-
|
|
7
|
-
```yaml
|
|
8
|
-
---
|
|
9
|
-
name: <one-line label>
|
|
10
|
-
description: <short blurb shown in the index>
|
|
11
|
-
type: <preference|interest|fact|reference>
|
|
12
|
-
---
|
|
13
|
-
<optional longer body>
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Pick the type:
|
|
17
|
-
|
|
18
|
-
- `preference` — durable habit, preference, or convention. Examples: "uses yarn (npm not allowed)", "prefers Emacs", "writes commits in English".
|
|
19
|
-
- `interest` — a topic, hobby, or domain followed long-term. Examples: "AI research papers", "robotics", "Impressionist painting".
|
|
20
|
-
- `fact` — a concrete personal fact that could become stale over time. Examples: "planning a trip to Egypt", "owns a toaster oven", "currently working on BootCamp project".
|
|
21
|
-
- `reference` — pointer to an internal/external resource. Examples: "main repo at ~/ss/llm/mulmoclaude4", "weekly art-exhibitions-watch task".
|
|
22
|
-
|
|
23
|
-
Filename convention: `<type>_<short-slug>.md` (lowercase ASCII, hyphenated). The frontmatter `type` is the source of truth — the filename is just for ergonomics. After writing the entry, also add a 1-line entry to `conversations/memory/MEMORY.md` of the form:
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
- [<name>](<filename>) — <description>
|
|
27
|
-
```
|
|
3
|
+
When you learn something from the conversation that would be useful to remember in future sessions, silently save it as a typed entry under `conversations/memory/`. Do not ask permission — just write it. Each entry is one file, `<type>_<short-slug>.md`, with `name` / `description` / `type` frontmatter (type: `preference`, `interest`, `fact` or `reference`), plus a 1-line entry in `conversations/memory/MEMORY.md`. Before writing, `Read` `config/helps/memory-atomic.md` for the exact format, how to pick the type, and the index-line form.
|
|
28
4
|
|
|
29
5
|
Write when: the fact is durable, not derivable from code or git history, and not already covered by an existing entry. Update an existing entry (and its index line) instead of creating a near-duplicate.
|
|
30
6
|
|
|
31
7
|
Skip when: it is ephemeral task state, sensitive (credentials, `~/.ssh`, tokens), a duplicate, or something the user asked you to forget.
|
|
32
|
-
|
|
33
|
-
Keep entries short — name + description + a few lines of body at most. Bias toward fewer high-signal entries rather than exhaustive logging.
|
|
@@ -1,60 +1,11 @@
|
|
|
1
1
|
## Memory Management
|
|
2
2
|
|
|
3
|
-
When you learn something from the conversation that would be useful to remember in future sessions, silently save it under `conversations/memory/`. Do not ask permission — just write it.
|
|
4
|
-
|
|
5
|
-
Memory is organised by **topic file**. Each file lives at `conversations/memory/<type>/<topic>.md` and groups related bullets under H2 sections. The system prompt's Memory section above shows the existing topics — pick from that list when adding a new bullet, and only create a new topic when nothing fits.
|
|
6
|
-
|
|
7
|
-
### Using memory proactively
|
|
8
|
-
|
|
9
|
-
Before answering, scan the Memory section above for topics related to the user's current message. The H2 tags after each `<type>/<topic>.md` line are searchable hints — match against the user's words (e.g. art / music / travel / tooling). When a topic looks relevant, `Read` the file first and weave the relevant bullets naturally into your answer. Examples:
|
|
10
|
-
|
|
11
|
-
- The user mentions a trip → check `fact/travel.md` (and any related interest topic) before suggesting destinations.
|
|
12
|
-
- The user asks about a tool / language → check `preference/dev.md` so you don't suggest something they've already vetoed.
|
|
13
|
-
- The user picks up a long-running project → check the matching `fact` or `reference` topic for prior context.
|
|
14
|
-
|
|
15
|
-
Do NOT announce that you are using memory ("according to your memory…"). The recall is for grounding your answer, not for narration. If nothing in memory is relevant, just answer normally.
|
|
16
|
-
|
|
17
|
-
Each topic file is one markdown document:
|
|
18
|
-
|
|
19
|
-
```yaml
|
|
20
|
-
---
|
|
21
|
-
type: <preference|interest|fact|reference>
|
|
22
|
-
topic: <slug>
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
# <Topic Name>
|
|
26
|
-
|
|
27
|
-
## <H2 Section>
|
|
28
|
-
- bullet
|
|
29
|
-
- another bullet
|
|
30
|
-
|
|
31
|
-
## <Another H2>
|
|
32
|
-
- bullet
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Pick the type:
|
|
36
|
-
|
|
37
|
-
- `preference` — durable habit, preference, or convention. Examples: yarn over npm, prefers Emacs, writes commits in English.
|
|
38
|
-
- `interest` — a topic, hobby, or domain followed long-term. Examples: AI research papers, robotics, Impressionist painting.
|
|
39
|
-
- `fact` — a concrete personal fact that could become stale over time. Examples: planning a trip to Egypt, owns a toaster oven, currently working on BootCamp project.
|
|
40
|
-
- `reference` — pointer to an internal/external resource. Examples: main repo path, weekly art-exhibitions-watch task.
|
|
41
|
-
|
|
42
|
-
Adding a new bullet:
|
|
43
|
-
|
|
44
|
-
1. Read the Memory section above. Find the topic file whose subject covers the new bullet.
|
|
45
|
-
2. `Read` that topic file. Pick the H2 section the bullet fits under (or add a new H2 if none fits — H2 sections are optional, you may also append directly under H1 for a small / new topic).
|
|
46
|
-
3. Append your bullet. Keep it short, one line ideally.
|
|
47
|
-
4. `Write` the file back.
|
|
48
|
-
5. `MEMORY.md` is rebuilt during clustering and on explicit `regenerateTopicIndex` calls; individual topic-file writes do NOT update the index immediately. If your bullet adds a new H2 that should appear in the index right away, also `Write` an updated `MEMORY.md` line for that topic.
|
|
49
|
-
|
|
50
|
-
Creating a new topic file:
|
|
51
|
-
|
|
52
|
-
- Filename: `<type>/<topic>.md` where `<topic>` is a short lowercase ASCII slug (a-z, 0-9, hyphenated). Examples: `interest/music.md`, `fact/travel.md`, `reference/tasks.md`.
|
|
53
|
-
- Body: H1 with a humanised topic name + bullet(s) under it. H2 sections are optional and best added once the topic has enough material to warrant grouping.
|
|
54
|
-
- After the topic file is written, also `Write` a matching line into `conversations/memory/MEMORY.md` so the new topic is discoverable in the next turn's Memory context. Same caveat as adding an H2: individual topic-file writes do NOT update `MEMORY.md` automatically — the index is only rebuilt during clustering or on explicit `regenerateTopicIndex` calls.
|
|
3
|
+
When you learn something from the conversation that would be useful to remember in future sessions, silently save it under `conversations/memory/`. Do not ask permission — just write it. Memory is organised as topic files at `conversations/memory/<type>/<topic>.md` (type: `preference`, `interest`, `fact` or `reference`); add to an existing topic from the Memory section and create a new one only when nothing fits. Before writing, `Read` `config/helps/memory-topic.md` for the file format, how to pick the type, and the steps — a new topic or H2 also needs a `conversations/memory/MEMORY.md` line.
|
|
55
4
|
|
|
56
5
|
Write when: the fact is durable, not derivable from code or git history, and not already covered by an existing bullet. Update an existing bullet instead of adding a near-duplicate.
|
|
57
6
|
|
|
58
7
|
Skip when: it is ephemeral task state, sensitive (credentials, `~/.ssh`, tokens), a duplicate, or something the user asked you to forget.
|
|
59
8
|
|
|
60
|
-
|
|
9
|
+
### Using memory proactively
|
|
10
|
+
|
|
11
|
+
Before answering, scan the Memory section above for topics related to the user's current message — the H2 tags after each `<type>/<topic>.md` line are searchable hints. When a topic looks relevant, `Read` the file first and weave the relevant bullets naturally into your answer. Do NOT announce that you are using memory ("according to your memory…"). If nothing in memory is relevant, just answer normally.
|
|
@@ -55,12 +55,9 @@ When you finish creating, updating, or surfacing a file in your reply (PDF, Mark
|
|
|
55
55
|
|
|
56
56
|
Before scheduling a skill / task (`schedule:` in SKILL.md frontmatter — `daily HH:MM` is **UTC**) or changing a system task's frequency, read `config/helps/scheduling.md` for the syntax, recommended intervals, and the overrides API.
|
|
57
57
|
|
|
58
|
-
## Collection custom views
|
|
58
|
+
## Collection custom views
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- **Desktop** (default): `config/helps/custom-view.md` — the view fetches records itself via the injected token + `dataUrl`.
|
|
63
|
-
- **Phone remote app** — when the user wants a view for the remote / mobile / phone app (リモート / スマホ / モバイル): `config/helps/custom-view-remote.md`. Register it with `target: "mobile"`; records arrive via `await __MC_VIEW.getItems(...)` over a postMessage bridge, and `fetch` is blocked entirely. Do NOT satisfy such a request by baking records into a standalone HTML artifact — author a `target: "mobile"` view (it auto-previews on the desktop in a phone-sized frame).
|
|
60
|
+
Before authoring or editing a collection custom view (an HTML file under the skill's `views/`, registered in `schema.json` `views[]`), read its help — the two contracts are incompatible: `config/helps/custom-view.md` (desktop, the default) or `config/helps/custom-view-remote.md` when the user wants it for the phone / remote / mobile app (リモート / スマホ / モバイル). For the phone, author a `target: "mobile"` view — never bake records into a standalone HTML artifact instead.
|
|
64
61
|
|
|
65
62
|
## When a tool call fails, or the user says something is broken
|
|
66
63
|
|
package/server/system/config.ts
CHANGED
|
@@ -117,6 +117,13 @@ export interface AppSettings {
|
|
|
117
117
|
// still wins, but an icon launch (#2613) passes neither, so for those
|
|
118
118
|
// users this setting is the only way to turn the sink off (#2617).
|
|
119
119
|
macosRemindersEnabled?: boolean;
|
|
120
|
+
|
|
121
|
+
// Whether the agent CLI loads Claude Code's USER-level settings (#3406):
|
|
122
|
+
// `~/.claude` skills, installed plugins and their hooks. On by default, so
|
|
123
|
+
// skills shared with Claude Code just work; off passes
|
|
124
|
+
// `--setting-sources project,local`, for a user whose plugins carry so many
|
|
125
|
+
// skills that their listing dominates every request.
|
|
126
|
+
loadClaudeUserSettings?: boolean;
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
const DEFAULT_SETTINGS: AppSettings = { extraAllowedTools: [] };
|
|
@@ -137,6 +144,7 @@ export const APP_SETTINGS_KEYS = [
|
|
|
137
144
|
"journal",
|
|
138
145
|
"pushEnabled",
|
|
139
146
|
"macosRemindersEnabled",
|
|
147
|
+
"loadClaudeUserSettings",
|
|
140
148
|
] as const satisfies readonly (keyof AppSettings)[];
|
|
141
149
|
|
|
142
150
|
export type AppSettingsKey = (typeof APP_SETTINGS_KEYS)[number];
|
|
@@ -155,6 +163,7 @@ export const SAFE_SETTINGS_KEYS = [
|
|
|
155
163
|
"journal",
|
|
156
164
|
"pushEnabled",
|
|
157
165
|
"macosRemindersEnabled",
|
|
166
|
+
"loadClaudeUserSettings",
|
|
158
167
|
] as const satisfies readonly AppSettingsKey[];
|
|
159
168
|
|
|
160
169
|
export type SafeSettingsKey = (typeof SAFE_SETTINGS_KEYS)[number];
|
|
@@ -236,6 +245,7 @@ const OPTIONAL_SETTING_VALIDATORS: Record<OptionalAppSettingsKey, (value: unknow
|
|
|
236
245
|
journal: optional(isJournalMode),
|
|
237
246
|
pushEnabled: isOptionalBoolean,
|
|
238
247
|
macosRemindersEnabled: isOptionalBoolean,
|
|
248
|
+
loadClaudeUserSettings: isOptionalBoolean,
|
|
239
249
|
};
|
|
240
250
|
|
|
241
251
|
function hasValidOptionalAppSettings(value: Record<string, unknown>): boolean {
|
|
@@ -301,6 +311,8 @@ const isOptionalNullableChatModel = (value: unknown): boolean => value === undef
|
|
|
301
311
|
const isOptionalNullableChatIndexMode = (value: unknown): boolean => value === undefined || value === null || isChatIndexMode(value);
|
|
302
312
|
const isOptionalNullableJournalMode = (value: unknown): boolean => value === undefined || value === null || isJournalMode(value);
|
|
303
313
|
|
|
314
|
+
const PATCH_BOOLEAN_KEYS = ["pushEnabled", "macosRemindersEnabled", "loadClaudeUserSettings"] as const satisfies readonly AppSettingsKey[];
|
|
315
|
+
|
|
304
316
|
export function isAppSettingsPatch(value: unknown): value is AppSettingsPatch {
|
|
305
317
|
if (!isRecord(value)) return false;
|
|
306
318
|
if (value.extraAllowedTools !== undefined && !isStringArray(value.extraAllowedTools)) return false;
|
|
@@ -311,9 +323,7 @@ export function isAppSettingsPatch(value: unknown): value is AppSettingsPatch {
|
|
|
311
323
|
if (value.voiceInput !== undefined && !isVoiceInputSettings(value.voiceInput)) return false;
|
|
312
324
|
if (!isOptionalNullableChatIndexMode(value.chatIndex)) return false;
|
|
313
325
|
if (!isOptionalNullableJournalMode(value.journal)) return false;
|
|
314
|
-
|
|
315
|
-
if (!isOptionalBoolean(value.macosRemindersEnabled)) return false;
|
|
316
|
-
return true;
|
|
326
|
+
return PATCH_BOOLEAN_KEYS.every((key) => isOptionalBoolean(value[key]));
|
|
317
327
|
}
|
|
318
328
|
|
|
319
329
|
function parseSettingsRaw(raw: string, file: string): unknown {
|
|
@@ -361,6 +371,9 @@ function cloneAppSettings(settings: AppSettings): AppSettings {
|
|
|
361
371
|
if (settings.macosRemindersEnabled !== undefined) {
|
|
362
372
|
copy.macosRemindersEnabled = settings.macosRemindersEnabled;
|
|
363
373
|
}
|
|
374
|
+
if (settings.loadClaudeUserSettings !== undefined) {
|
|
375
|
+
copy.loadClaudeUserSettings = settings.loadClaudeUserSettings;
|
|
376
|
+
}
|
|
364
377
|
return copy;
|
|
365
378
|
}
|
|
366
379
|
|
|
@@ -401,6 +414,12 @@ export function isMacosRemindersEnabled(settings: AppSettings): boolean {
|
|
|
401
414
|
return settings.macosRemindersEnabled ?? true;
|
|
402
415
|
}
|
|
403
416
|
|
|
417
|
+
/** Whether the agent CLI loads Claude Code's user-level settings, default
|
|
418
|
+
* `true` — MulmoClaude has always read `~/.claude` skills and plugins. */
|
|
419
|
+
export function isClaudeUserSettingsEnabled(settings: AppSettings): boolean {
|
|
420
|
+
return settings.loadClaudeUserSettings ?? true;
|
|
421
|
+
}
|
|
422
|
+
|
|
404
423
|
export function loadSettings(): AppSettings {
|
|
405
424
|
const file = settingsPath();
|
|
406
425
|
const raw = readTextSafeSync(file);
|
|
@@ -458,6 +477,9 @@ export function saveSettings(settings: AppSettings): void {
|
|
|
458
477
|
if (settings.macosRemindersEnabled !== undefined) {
|
|
459
478
|
payload.macosRemindersEnabled = settings.macosRemindersEnabled;
|
|
460
479
|
}
|
|
480
|
+
if (settings.loadClaudeUserSettings !== undefined) {
|
|
481
|
+
payload.loadClaudeUserSettings = settings.loadClaudeUserSettings;
|
|
482
|
+
}
|
|
461
483
|
const serialised = JSON.stringify(payload, null, 2);
|
|
462
484
|
writeFileAtomicSync(settingsPath(), `${serialised}\n`, { mode: 0o600 });
|
|
463
485
|
}
|
|
@@ -32,6 +32,31 @@
|
|
|
32
32
|
<p class="text-xs text-gray-500">{{ t("settingsModal.modelTab.helperText") }}</p>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
|
+
<div class="flex items-start gap-3 border-t border-gray-200 pt-3">
|
|
36
|
+
<input
|
|
37
|
+
id="settings-model-user-settings"
|
|
38
|
+
v-model="userSettingsEnabled"
|
|
39
|
+
type="checkbox"
|
|
40
|
+
class="mt-1 h-4 w-4"
|
|
41
|
+
:disabled="savingUserSettings || !loaded"
|
|
42
|
+
data-testid="settings-model-user-settings-input"
|
|
43
|
+
@change="saveUserSettings"
|
|
44
|
+
/>
|
|
45
|
+
<label for="settings-model-user-settings" class="flex-1">
|
|
46
|
+
<span class="block text-sm font-medium text-gray-800">{{ t("settingsModal.modelTab.userSettingsLabel") }}</span>
|
|
47
|
+
<span class="block text-xs text-gray-500 mt-0.5">{{ t("settingsModal.modelTab.userSettingsHint") }}</span>
|
|
48
|
+
<span v-if="skillListing" class="block text-xs text-gray-600 mt-1" data-testid="settings-model-skill-listing">
|
|
49
|
+
{{ t("settingsModal.modelTab.skillListingSummary", { count: skillListing.skillCount }) }}
|
|
50
|
+
<template v-if="skillListing.pluginNames.length > 0">
|
|
51
|
+
{{ t("settingsModal.modelTab.skillListingPlugins", { plugins: skillListing.pluginNames.join(", ") }) }}
|
|
52
|
+
</template>
|
|
53
|
+
</span>
|
|
54
|
+
<span v-if="skillListing?.heavy" class="block text-xs text-amber-700 mt-1" data-testid="settings-model-skill-listing-heavy">
|
|
55
|
+
{{ t("settingsModal.modelTab.skillListingHeavy") }}
|
|
56
|
+
</span>
|
|
57
|
+
</label>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
35
60
|
<div v-if="loaded && !errorMessage" class="flex items-center gap-3 text-xs">
|
|
36
61
|
<span :class="colourOf(modelField)" data-testid="settings-model-model-status">
|
|
37
62
|
{{ modelStatusText }}
|
|
@@ -63,8 +88,15 @@ const emit = defineEmits<{
|
|
|
63
88
|
saved: [];
|
|
64
89
|
}>();
|
|
65
90
|
|
|
91
|
+
interface SkillListing {
|
|
92
|
+
skillCount: number;
|
|
93
|
+
pluginNames: string[];
|
|
94
|
+
heavy: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
66
97
|
interface SettingsResponse {
|
|
67
|
-
settings: { extraAllowedTools: string[]; effortLevel?: EffortLevel; chatModel?: ChatModel };
|
|
98
|
+
settings: { extraAllowedTools: string[]; effortLevel?: EffortLevel; chatModel?: ChatModel; loadClaudeUserSettings?: boolean };
|
|
99
|
+
skillListing?: SkillListing | null;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
// One select's whole state, so the save dance below is written once
|
|
@@ -87,6 +119,12 @@ const storedModel = ref<ChatModel | "">("");
|
|
|
87
119
|
const savingModel = ref(false);
|
|
88
120
|
const modelField: SettingField<ChatModel> = { key: "chatModel", draft: modelDraft, stored: storedModel, saving: savingModel };
|
|
89
121
|
|
|
122
|
+
// Default true matches `isClaudeUserSettingsEnabled` on the server.
|
|
123
|
+
const userSettingsEnabled = ref(true);
|
|
124
|
+
const storedUserSettings = ref(true);
|
|
125
|
+
const savingUserSettings = ref(false);
|
|
126
|
+
const skillListing = ref<SkillListing | null>(null);
|
|
127
|
+
|
|
90
128
|
const loaded = ref(false);
|
|
91
129
|
const errorMessage = ref("");
|
|
92
130
|
|
|
@@ -119,6 +157,9 @@ async function load(): Promise<void> {
|
|
|
119
157
|
effortDraft.value = storedEffort.value;
|
|
120
158
|
storedModel.value = response.data.settings.chatModel ?? "";
|
|
121
159
|
modelDraft.value = storedModel.value;
|
|
160
|
+
storedUserSettings.value = response.data.settings.loadClaudeUserSettings ?? true;
|
|
161
|
+
userSettingsEnabled.value = storedUserSettings.value;
|
|
162
|
+
skillListing.value = response.data.skillListing ?? null;
|
|
122
163
|
loaded.value = true;
|
|
123
164
|
}
|
|
124
165
|
|
|
@@ -150,6 +191,22 @@ async function save<T extends string>(field: SettingField<T>): Promise<void> {
|
|
|
150
191
|
}
|
|
151
192
|
}
|
|
152
193
|
|
|
194
|
+
async function saveUserSettings(): Promise<void> {
|
|
195
|
+
if (savingUserSettings.value || userSettingsEnabled.value === storedUserSettings.value) return;
|
|
196
|
+
const requested = userSettingsEnabled.value;
|
|
197
|
+
savingUserSettings.value = true;
|
|
198
|
+
errorMessage.value = "";
|
|
199
|
+
const response = await apiPut<unknown>(API_ROUTES.config.settings, { loadClaudeUserSettings: requested });
|
|
200
|
+
savingUserSettings.value = false;
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
errorMessage.value = response.error || t("settingsModal.modelTab.saveError");
|
|
203
|
+
userSettingsEnabled.value = storedUserSettings.value;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
storedUserSettings.value = requested;
|
|
207
|
+
emit("saved");
|
|
208
|
+
}
|
|
209
|
+
|
|
153
210
|
watch(
|
|
154
211
|
() => props.reloadToken,
|
|
155
212
|
() => {
|
package/src/config/roles.ts
CHANGED
|
@@ -42,6 +42,9 @@ export const RoleSchema = z.object({
|
|
|
42
42
|
icon: z.string(),
|
|
43
43
|
prompt: z.string(),
|
|
44
44
|
availablePlugins: availablePluginsSchema,
|
|
45
|
+
// `alwaysActive` tools reach every role regardless of `availablePlugins`; a
|
|
46
|
+
// role that has no use for one names it here to keep its prompt smaller.
|
|
47
|
+
excludedAlwaysActiveTools: z.array(z.string()).optional(),
|
|
45
48
|
queries: z.array(z.string()).optional(),
|
|
46
49
|
isDebugRole: z.boolean().optional(),
|
|
47
50
|
// Model family this role's sessions run on (#3104). Absent means the role
|
|
@@ -96,7 +99,7 @@ export const ROLES: [Role, ...Role[]] = [
|
|
|
96
99
|
},
|
|
97
100
|
{
|
|
98
101
|
// Keeps the per-turn system prompt small for plain conversation (#3369):
|
|
99
|
-
// only the form and collection display tools, plus
|
|
102
|
+
// only the form and collection display tools, plus `manageCollection`.
|
|
100
103
|
id: "simple",
|
|
101
104
|
name: "Simple",
|
|
102
105
|
icon: "chat_bubble",
|
|
@@ -104,6 +107,8 @@ export const ROLES: [Role, ...Role[]] = [
|
|
|
104
107
|
"You are a concise conversational assistant. Answer directly in the chat, and use the workspace files and collections when the question needs them.\n\n" +
|
|
105
108
|
"This role carries few display tools to keep each turn light. When the user wants a document, slides, an image, or an HTML page, say that the General role can make it and suggest switching.",
|
|
106
109
|
availablePlugins: [TOOL_NAMES.presentForm, TOOL_NAMES.presentCollection],
|
|
110
|
+
// Plain conversation never pre-generates artifacts in the background.
|
|
111
|
+
excludedAlwaysActiveTools: [TOOL_NAMES.spawnBackgroundChat],
|
|
107
112
|
queries: ["What can you help me with in this role?", "Summarise what's in my collections.", "Help me think through a decision I'm stuck on."],
|
|
108
113
|
},
|
|
109
114
|
{
|
package/src/lang/de.ts
CHANGED
|
@@ -388,6 +388,12 @@ const deMessages = {
|
|
|
388
388
|
helperText: "Höhere Stufen erlauben mehr Denkzeit, erhöhen aber Latenz und Token-Verbrauch.",
|
|
389
389
|
configured: "Effort: {level}",
|
|
390
390
|
notConfigured: "Nicht gesetzt",
|
|
391
|
+
userSettingsLabel: "Benutzereinstellungen und Plugins von Claude Code laden",
|
|
392
|
+
userSettingsHint:
|
|
393
|
+
"Liest Skills, Plugins und Hooks aus ~/.claude – dieselben, die Claude Code verwendet. Schalten Sie dies aus, wenn installierte Plugins so viele Skills mitbringen, dass jede Anfrage schwer wird; die Skills des Workspace bleiben nutzbar. Gilt ab der nächsten Nachricht. Ausgeschaltet folgt ein nicht gesetztes Modell auch nicht mehr ~/.claude/settings.json.",
|
|
394
|
+
skillListingSummary: "Im letzten Durchgang geladene Skills: {count}.",
|
|
395
|
+
skillListingPlugins: "Aus Plugins: {plugins}.",
|
|
396
|
+
skillListingHeavy: "So viele Skills machen jede Anfrage schwer; ausschalten entfernt die aus ~/.claude.",
|
|
391
397
|
loadError: "Einstellungen konnten nicht geladen werden",
|
|
392
398
|
saveError: "Speichern fehlgeschlagen",
|
|
393
399
|
},
|
package/src/lang/en.ts
CHANGED
|
@@ -406,6 +406,12 @@ const enMessages = {
|
|
|
406
406
|
helperText: "Higher levels allow more thinking time but increase latency and token usage.",
|
|
407
407
|
configured: "Effort: {level}",
|
|
408
408
|
notConfigured: "Not set",
|
|
409
|
+
userSettingsLabel: "Load Claude Code's user settings and plugins",
|
|
410
|
+
userSettingsHint:
|
|
411
|
+
"Reads skills, plugins and hooks from ~/.claude, the same ones Claude Code uses. Turn this off if your installed plugins carry so many skills that every request gets heavy; the workspace's own skills keep working. Takes effect from the next message. When off, an unset model no longer follows ~/.claude/settings.json either.",
|
|
412
|
+
skillListingSummary: "Skills listed on the last turn: {count}.",
|
|
413
|
+
skillListingPlugins: "From plugins: {plugins}.",
|
|
414
|
+
skillListingHeavy: "That many skills make every request heavy; turning this off drops the ones from ~/.claude.",
|
|
409
415
|
loadError: "Failed to load settings",
|
|
410
416
|
saveError: "Failed to save",
|
|
411
417
|
},
|
package/src/lang/es.ts
CHANGED
|
@@ -390,6 +390,12 @@ const esMessages = {
|
|
|
390
390
|
helperText: "Niveles más altos permiten más tiempo de pensamiento pero aumentan la latencia y el uso de tokens.",
|
|
391
391
|
configured: "Esfuerzo: {level}",
|
|
392
392
|
notConfigured: "Sin configurar",
|
|
393
|
+
userSettingsLabel: "Cargar los ajustes de usuario y los plugins de Claude Code",
|
|
394
|
+
userSettingsHint:
|
|
395
|
+
"Lee las skills, los plugins y los hooks de ~/.claude, los mismos que usa Claude Code. Desactívalo si los plugins instalados traen tantas skills que cada petición se vuelve pesada; las skills del propio espacio de trabajo siguen funcionando. Se aplica desde el siguiente mensaje. Si está desactivado, un modelo sin configurar ya no sigue ~/.claude/settings.json.",
|
|
396
|
+
skillListingSummary: "Skills cargadas en el último turno: {count}.",
|
|
397
|
+
skillListingPlugins: "De los plugins: {plugins}.",
|
|
398
|
+
skillListingHeavy: "Con tantas skills cada petición pesa más; al desactivarlo se quitan las de ~/.claude.",
|
|
393
399
|
loadError: "Error al cargar los ajustes",
|
|
394
400
|
saveError: "Error al guardar",
|
|
395
401
|
},
|
package/src/lang/fr.ts
CHANGED
|
@@ -386,6 +386,12 @@ const frMessages = {
|
|
|
386
386
|
helperText: "Les niveaux plus élevés autorisent plus de temps de réflexion mais augmentent la latence et la consommation de tokens.",
|
|
387
387
|
configured: "Effort : {level}",
|
|
388
388
|
notConfigured: "Non défini",
|
|
389
|
+
userSettingsLabel: "Charger les réglages utilisateur et les plugins de Claude Code",
|
|
390
|
+
userSettingsHint:
|
|
391
|
+
"Lit les skills, plugins et hooks de ~/.claude, les mêmes que ceux de Claude Code. Désactivez-le si les plugins installés apportent tant de skills que chaque requête devient lourde ; les skills de l'espace de travail restent disponibles. Prend effet à partir du message suivant. Désactivé, un modèle non défini ne suit plus non plus ~/.claude/settings.json.",
|
|
392
|
+
skillListingSummary: "Skills chargées au dernier tour : {count}.",
|
|
393
|
+
skillListingPlugins: "Depuis les plugins : {plugins}.",
|
|
394
|
+
skillListingHeavy: "Autant de skills alourdissent chaque requête ; le désactiver retire celles de ~/.claude.",
|
|
389
395
|
loadError: "Échec du chargement des paramètres",
|
|
390
396
|
saveError: "Échec de l'enregistrement",
|
|
391
397
|
},
|
package/src/lang/ja.ts
CHANGED
|
@@ -385,6 +385,12 @@ const jaMessages = {
|
|
|
385
385
|
helperText: "高いレベルほど思考時間が長くなりますが、レイテンシとトークン消費も増えます。",
|
|
386
386
|
configured: "Effort: {level}",
|
|
387
387
|
notConfigured: "未設定",
|
|
388
|
+
userSettingsLabel: "Claude Code のユーザー設定とプラグインを読み込む",
|
|
389
|
+
userSettingsHint:
|
|
390
|
+
"~/.claude にある、Claude Code と共通のスキル・プラグイン・フックを読み込みます。インストールしたプラグインのスキルが多く、毎回の依頼が重くなるときはオフにしてください。ワークスペースのスキルはそのまま使えます。次のメッセージから反映されます。オフのときは、モデルが未設定でも ~/.claude/settings.json のモデル指定は使われません。",
|
|
391
|
+
skillListingSummary: "前回のターンで読み込まれたスキル: {count} 個。",
|
|
392
|
+
skillListingPlugins: "プラグイン: {plugins}。",
|
|
393
|
+
skillListingHeavy: "スキルが多いため、毎回の依頼が重くなっています。オフにすると ~/.claude の分が外れます。",
|
|
388
394
|
loadError: "設定の読み込みに失敗しました",
|
|
389
395
|
saveError: "保存に失敗しました",
|
|
390
396
|
},
|
package/src/lang/ko.ts
CHANGED
|
@@ -385,6 +385,12 @@ const koMessages = {
|
|
|
385
385
|
helperText: "레벨이 높을수록 사고 시간이 늘어나지만 지연 시간과 토큰 사용량도 증가합니다.",
|
|
386
386
|
configured: "Effort: {level}",
|
|
387
387
|
notConfigured: "미설정",
|
|
388
|
+
userSettingsLabel: "Claude Code 사용자 설정과 플러그인 불러오기",
|
|
389
|
+
userSettingsHint:
|
|
390
|
+
"~/.claude에 있는, Claude Code와 공유하는 스킬·플러그인·훅을 불러옵니다. 설치한 플러그인의 스킬이 너무 많아 요청마다 무거워진다면 끄세요. 워크스페이스 자체 스킬은 그대로 사용할 수 있습니다. 다음 메시지부터 적용됩니다. 끄면 모델이 미설정이어도 ~/.claude/settings.json의 모델 지정을 따르지 않습니다.",
|
|
391
|
+
skillListingSummary: "지난 턴에 불러온 스킬: {count}개.",
|
|
392
|
+
skillListingPlugins: "플러그인: {plugins}.",
|
|
393
|
+
skillListingHeavy: "스킬이 많아 요청마다 무거워지고 있습니다. 끄면 ~/.claude의 스킬이 빠집니다.",
|
|
388
394
|
loadError: "설정을 불러오지 못했습니다",
|
|
389
395
|
saveError: "저장에 실패했습니다",
|
|
390
396
|
},
|
package/src/lang/pt-BR.ts
CHANGED
|
@@ -384,6 +384,12 @@ const ptBRMessages = {
|
|
|
384
384
|
helperText: "Níveis mais altos permitem mais tempo de pensamento mas aumentam latência e uso de tokens.",
|
|
385
385
|
configured: "Esforço: {level}",
|
|
386
386
|
notConfigured: "Sem configurar",
|
|
387
|
+
userSettingsLabel: "Carregar as configurações de usuário e os plugins do Claude Code",
|
|
388
|
+
userSettingsHint:
|
|
389
|
+
"Lê as skills, os plugins e os hooks de ~/.claude, os mesmos que o Claude Code usa. Desative se os plugins instalados trazem tantas skills que toda solicitação fica pesada; as skills do próprio workspace continuam funcionando. Vale a partir da próxima mensagem. Desativado, um modelo não definido também deixa de seguir ~/.claude/settings.json.",
|
|
390
|
+
skillListingSummary: "Skills carregadas no último turno: {count}.",
|
|
391
|
+
skillListingPlugins: "Dos plugins: {plugins}.",
|
|
392
|
+
skillListingHeavy: "Com tantas skills toda solicitação fica pesada; ao desativar, as de ~/.claude saem.",
|
|
387
393
|
loadError: "Falha ao carregar as configurações",
|
|
388
394
|
saveError: "Falha ao salvar",
|
|
389
395
|
},
|
package/src/lang/zh.ts
CHANGED
|
@@ -375,6 +375,12 @@ const zhMessages = {
|
|
|
375
375
|
helperText: "更高的等级会带来更多思考时间,但也会增加延迟和 token 消耗。",
|
|
376
376
|
configured: "推理强度:{level}",
|
|
377
377
|
notConfigured: "未设置",
|
|
378
|
+
userSettingsLabel: "加载 Claude Code 的用户设置和插件",
|
|
379
|
+
userSettingsHint:
|
|
380
|
+
"读取 ~/.claude 中与 Claude Code 共用的技能、插件和钩子。如果已安装插件的技能太多、导致每次请求都很重,请关闭此项;工作区自己的技能仍可使用。从下一条消息开始生效。关闭时,即使未设置模型,也不会再使用 ~/.claude/settings.json 中的模型。",
|
|
381
|
+
skillListingSummary: "上一轮加载的技能:{count} 个。",
|
|
382
|
+
skillListingPlugins: "来自插件:{plugins}。",
|
|
383
|
+
skillListingHeavy: "技能太多会让每次请求都变重;关闭此项可去掉来自 ~/.claude 的技能。",
|
|
378
384
|
loadError: "加载设置失败",
|
|
379
385
|
saveError: "保存失败",
|
|
380
386
|
},
|
|
@@ -45,6 +45,12 @@ const toolDefinition: ToolDefinition = {
|
|
|
45
45
|
description:
|
|
46
46
|
"Optional model family for this role's sessions — one of the aliases offered in Settings \u2192 Model. Omit it to follow the app-wide choice. A value that is not a known alias is ignored. ALWAYS echo back the role's existing value when updating a role, or the setting is lost.",
|
|
47
47
|
},
|
|
48
|
+
excludedAlwaysActiveTools: {
|
|
49
|
+
type: "array",
|
|
50
|
+
items: { type: "string" },
|
|
51
|
+
description:
|
|
52
|
+
"Optional names of always-active tools (offered to every role regardless of availablePlugins) that this role opts out of. ALWAYS echo back the role's existing value when updating a role, or the setting is lost.",
|
|
53
|
+
},
|
|
48
54
|
},
|
|
49
55
|
required: ["id", "name", "icon", "prompt", "availablePlugins"],
|
|
50
56
|
},
|
|
@@ -18,6 +18,9 @@ export interface CustomRole {
|
|
|
18
18
|
* Must survive every edit path — the reason this is a declared field at all
|
|
19
19
|
* is that the role object is rebuilt field by field in several places. */
|
|
20
20
|
model?: string;
|
|
21
|
+
/** Always-active tools this role opts out of. No editor in the form, so it
|
|
22
|
+
* is carried through every edit path the same way `model` is. */
|
|
23
|
+
excludedAlwaysActiveTools?: string[];
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export interface ManageRolesData {
|
|
@@ -17,13 +17,14 @@ const ROLE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
|
17
17
|
// the DOM, so a missing field has to fail here, not in a template.
|
|
18
18
|
const parseCustomRole = (value: unknown): CustomRole | null => {
|
|
19
19
|
if (!isRecord(value)) return null;
|
|
20
|
-
const { id, name, icon, prompt, availablePlugins, queries, model } = value;
|
|
20
|
+
const { id, name, icon, prompt, availablePlugins, queries, model, excludedAlwaysActiveTools } = value;
|
|
21
21
|
if (typeof id !== "string" || typeof name !== "string" || typeof icon !== "string" || typeof prompt !== "string") return null;
|
|
22
22
|
if (!isStringArray(availablePlugins)) return null;
|
|
23
23
|
const base: CustomRole = { id, name, icon, prompt, availablePlugins };
|
|
24
24
|
// A non-string model is dropped, not fatal: a malformed field should cost
|
|
25
25
|
// that field, not make the whole role vanish from the list.
|
|
26
|
-
const
|
|
26
|
+
const withModel: CustomRole = isStoredModel(model) ? { ...base, model } : base;
|
|
27
|
+
const role: CustomRole = isStringArray(excludedAlwaysActiveTools) ? { ...withModel, excludedAlwaysActiveTools } : withModel;
|
|
27
28
|
return isStringArray(queries) ? { ...role, queries } : role;
|
|
28
29
|
};
|
|
29
30
|
|
|
@@ -54,6 +55,8 @@ export interface RoleForm {
|
|
|
54
55
|
queriesText: string;
|
|
55
56
|
/** `""` is "not set" — the app-wide setting decides. */
|
|
56
57
|
model: string;
|
|
58
|
+
/** Not editable in the form; carried so saving an edit does not drop it. */
|
|
59
|
+
excludedAlwaysActiveTools: string[];
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export type RoleFormErrorCode = "idRequired" | "idInvalid" | "nameRequired" | "idDuplicate";
|
|
@@ -87,6 +90,7 @@ export const formToRole = (form: RoleForm): CustomRole => ({
|
|
|
87
90
|
// Omitted rather than set to "" when unset: the role file should carry no
|
|
88
91
|
// key at all, so `role.model ?? settings.chatModel` falls through cleanly.
|
|
89
92
|
...(form.model ? { model: form.model } : {}),
|
|
93
|
+
...(form.excludedAlwaysActiveTools.length > 0 ? { excludedAlwaysActiveTools: form.excludedAlwaysActiveTools } : {}),
|
|
90
94
|
});
|
|
91
95
|
|
|
92
96
|
/** A blank form. One factory so a field added to `RoleForm` cannot be
|
|
@@ -107,6 +111,7 @@ export const emptyRoleForm = (): RoleForm => ({
|
|
|
107
111
|
selectedPlugins: [],
|
|
108
112
|
queriesText: "",
|
|
109
113
|
model: "",
|
|
114
|
+
excludedAlwaysActiveTools: [],
|
|
110
115
|
});
|
|
111
116
|
|
|
112
117
|
export const roleToForm = (role: CustomRole): RoleForm => ({
|
|
@@ -117,6 +122,7 @@ export const roleToForm = (role: CustomRole): RoleForm => ({
|
|
|
117
122
|
selectedPlugins: [...role.availablePlugins],
|
|
118
123
|
queriesText: (role.queries ?? []).join("\n"),
|
|
119
124
|
model: role.model ?? "",
|
|
125
|
+
excludedAlwaysActiveTools: [...(role.excludedAlwaysActiveTools ?? [])],
|
|
120
126
|
});
|
|
121
127
|
|
|
122
128
|
// `excludeId` lets rename skip the role's own id when checking for duplicates.
|