mulmoclaude 0.6.2 → 0.6.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 (182) hide show
  1. package/README.md +26 -0
  2. package/bin/mulmoclaude.js +11 -1
  3. package/client/assets/JsonEditor-D6WBWLoa.js +10 -0
  4. package/client/assets/JsonEditor-Di5xGeZY.css +1 -0
  5. package/client/assets/_plugin-vue_export-helper-BOai-rQB.js +1 -0
  6. package/client/assets/chunk-D8eiyYIV-LcKZGJv5.js +1 -0
  7. package/client/assets/{html2canvas-CDGcmOD3-Bkf2uOth.js → html2canvas-CDGcmOD3-XVrO-eyz.js} +1 -1
  8. package/client/assets/index-CyBr8Mkr.css +2 -0
  9. package/client/assets/index-zZIqEbNX.js +5106 -0
  10. package/client/assets/{index.es-DqtpmBm8-D9mAh_KQ.js → index.es-DqtpmBm8-DHT6q10o.js} +1 -1
  11. package/client/assets/material-symbols-outlined-DtIK7AQn.woff2 +0 -0
  12. package/client/assets/runtime-protocol-vue-D6kcV0wa.js +1 -0
  13. package/client/assets/{runtime-vue-BVUzgYGA.js → runtime-vue-fFYhnNg3.js} +1 -1
  14. package/client/assets/{vue-C8UuIO9J.js → vue-D4w8THF_.js} +1 -1
  15. package/client/assets/vue-i18n-CQbxVmNs.js +3 -0
  16. package/client/assets/vue.runtime.esm-bundler-BTyIdNAI.js +4 -0
  17. package/client/index.html +10 -10
  18. package/package.json +9 -8
  19. package/server/agent/backend/claude-code.ts +34 -0
  20. package/server/agent/backend/fake-echo.ts +370 -0
  21. package/server/agent/backend/index.ts +16 -1
  22. package/server/agent/config.ts +74 -24
  23. package/server/agent/index.ts +104 -80
  24. package/server/agent/mcpFailureMonitor.ts +167 -0
  25. package/server/agent/mcpPreflight.ts +185 -0
  26. package/server/agent/prompt.ts +50 -359
  27. package/server/agent/stdioHttpShim.ts +171 -0
  28. package/server/agent/stream.ts +12 -1
  29. package/server/api/routes/encore.ts +55 -0
  30. package/server/api/routes/files.ts +22 -0
  31. package/server/api/routes/mulmo-script.ts +19 -1
  32. package/server/api/routes/schedulerHandlers.ts +52 -4
  33. package/server/api/routes/sessions.ts +15 -0
  34. package/server/api/routes/skills.ts +263 -0
  35. package/server/build/dispatcher.mjs +299 -0
  36. package/server/encore/INVARIANTS.md +272 -0
  37. package/server/encore/boot.ts +39 -0
  38. package/server/encore/closure.ts +36 -0
  39. package/server/encore/cycle.ts +276 -0
  40. package/server/encore/dispatch.ts +103 -0
  41. package/server/encore/handlers/amend.ts +99 -0
  42. package/server/encore/handlers/appendNote.ts +74 -0
  43. package/server/encore/handlers/defineEncore.ts +42 -0
  44. package/server/encore/handlers/listTickets.ts +107 -0
  45. package/server/encore/handlers/markStepDone.ts +41 -0
  46. package/server/encore/handlers/markTargetSkipped.ts +33 -0
  47. package/server/encore/handlers/query.ts +138 -0
  48. package/server/encore/handlers/recordValues.ts +44 -0
  49. package/server/encore/handlers/resolveNotification.ts +121 -0
  50. package/server/encore/handlers/setup.ts +81 -0
  51. package/server/encore/handlers/shared.ts +137 -0
  52. package/server/encore/handlers/snooze.ts +87 -0
  53. package/server/encore/handlers/startObligationChat.ts +64 -0
  54. package/server/encore/handlers/startSetupChat.ts +50 -0
  55. package/server/encore/lock.ts +61 -0
  56. package/server/encore/notifier.ts +123 -0
  57. package/server/encore/obligation.ts +25 -0
  58. package/server/encore/paths.ts +78 -0
  59. package/server/encore/reconcile.ts +661 -0
  60. package/server/encore/tick.ts +191 -0
  61. package/server/encore/yaml-fm.ts +63 -0
  62. package/server/events/notifications.ts +19 -91
  63. package/server/index.ts +94 -9
  64. package/server/notifier/engine.ts +102 -1
  65. package/server/notifier/macosReminderAdapter.ts +30 -0
  66. package/server/notifier/runtime-api.ts +41 -1
  67. package/server/notifier/types.ts +15 -2
  68. package/server/plugins/runtime.ts +11 -2
  69. package/server/prompts/index.ts +39 -0
  70. package/server/prompts/system/journal-pointer.md +12 -0
  71. package/server/prompts/system/memory-management-atomic.md +33 -0
  72. package/server/prompts/system/memory-management-topic.md +60 -0
  73. package/server/prompts/system/news-concierge.md +24 -0
  74. package/server/prompts/system/sandbox-tools.md +10 -0
  75. package/server/prompts/system/sources-context.md +16 -0
  76. package/server/prompts/system/system.md +91 -0
  77. package/server/system/announceOptionalDeps.ts +57 -0
  78. package/server/system/appVersion.ts +34 -0
  79. package/server/system/config.ts +17 -1
  80. package/server/system/docker.ts +14 -6
  81. package/server/system/env.ts +18 -5
  82. package/server/system/optionalDeps.ts +129 -0
  83. package/server/utils/cli-flags.d.mts +14 -0
  84. package/server/utils/cli-flags.mjs +53 -0
  85. package/server/utils/files/encore-io.ts +111 -0
  86. package/server/utils/time.ts +6 -0
  87. package/server/workspace/helps/business.md +2 -2
  88. package/server/workspace/helps/encore-dsl.md +482 -0
  89. package/server/workspace/helps/index.md +15 -13
  90. package/server/workspace/helps/mulmoscript.md +3 -3
  91. package/server/workspace/helps/sandbox.md +2 -2
  92. package/server/workspace/hooks/dispatcher.ts +7 -5
  93. package/server/workspace/hooks/provision.ts +6 -3
  94. package/server/workspace/paths.ts +13 -4
  95. package/server/workspace/skills/catalog.ts +355 -0
  96. package/server/workspace/skills/external/catalog.ts +283 -0
  97. package/server/workspace/skills/external/clone.ts +129 -0
  98. package/server/workspace/skills/external/id.ts +194 -0
  99. package/server/workspace/skills/external/install.ts +417 -0
  100. package/server/workspace/skills/external/presets.ts +50 -0
  101. package/server/workspace/skills-preset.ts +29 -17
  102. package/server/workspace/workspace.ts +10 -5
  103. package/src/App.vue +37 -8
  104. package/src/components/FileContentRenderer.vue +102 -9
  105. package/src/components/JsonEditor.vue +160 -0
  106. package/src/components/NotificationBell.vue +35 -3
  107. package/src/components/PluginLauncher.vue +20 -41
  108. package/src/components/RightSidebar.vue +19 -0
  109. package/src/components/SettingsMcpTab.vue +58 -11
  110. package/src/components/SettingsModal.vue +22 -1
  111. package/src/components/StackView.vue +10 -1
  112. package/src/components/TodoExplorer.vue +16 -0
  113. package/src/components/todo/TodoKanbanView.vue +34 -6
  114. package/src/composables/useNotifications.ts +21 -1
  115. package/src/config/apiRoutes.ts +0 -6
  116. package/src/config/mcpCatalog.ts +12 -7
  117. package/src/config/mcpTypes.ts +5 -0
  118. package/src/config/roles.ts +52 -15
  119. package/src/config/systemFileDescriptors.ts +12 -0
  120. package/src/lang/de.ts +108 -12
  121. package/src/lang/en.ts +105 -11
  122. package/src/lang/es.ts +106 -11
  123. package/src/lang/fr.ts +106 -11
  124. package/src/lang/ja.ts +104 -11
  125. package/src/lang/ko.ts +105 -11
  126. package/src/lang/pt-BR.ts +106 -11
  127. package/src/lang/zh.ts +103 -11
  128. package/src/main.ts +1 -0
  129. package/src/plugins/_generated/metas.ts +4 -0
  130. package/src/plugins/_generated/registrations.ts +2 -0
  131. package/src/plugins/_generated/server-bindings.ts +5 -0
  132. package/src/plugins/encore/EncoreDashboard.vue +504 -0
  133. package/src/plugins/encore/EncoreRedirect.vue +116 -0
  134. package/src/plugins/encore/View.vue +36 -0
  135. package/src/plugins/encore/defineEncoreDefinition.ts +74 -0
  136. package/src/plugins/encore/defineEncoreMeta.ts +13 -0
  137. package/src/plugins/encore/index.ts +93 -0
  138. package/src/plugins/encore/manageEncoreDefinition.ts +100 -0
  139. package/src/plugins/encore/manageEncoreMeta.ts +36 -0
  140. package/src/plugins/manageSkills/View.vue +832 -30
  141. package/src/plugins/manageSkills/categories.ts +125 -0
  142. package/src/plugins/manageSkills/meta.ts +30 -0
  143. package/src/plugins/markdown/definition.ts +3 -3
  144. package/src/plugins/meta-types.ts +5 -0
  145. package/src/plugins/presentMulmoScript/Preview.vue +3 -3
  146. package/src/plugins/presentMulmoScript/View.vue +157 -33
  147. package/src/plugins/presentMulmoScript/meta.ts +4 -0
  148. package/src/plugins/scheduler/View.vue +45 -9
  149. package/src/plugins/scheduler/calendarDefinition.ts +6 -2
  150. package/src/plugins/scheduler/multiDayHelpers.ts +95 -0
  151. package/src/plugins/skill/View.vue +1 -5
  152. package/src/plugins/spreadsheet/View.vue +3 -3
  153. package/src/plugins/spreadsheet/definition.ts +1 -1
  154. package/src/plugins/textResponse/Preview.vue +14 -1
  155. package/src/plugins/textResponse/View.vue +39 -24
  156. package/src/plugins/wiki/components/WikiPageBody.vue +4 -0
  157. package/src/router/index.ts +11 -0
  158. package/src/router/pageRoutes.ts +1 -0
  159. package/src/types/encore-dsl/at-expression.ts +120 -0
  160. package/src/types/encore-dsl/at-resolver.ts +32 -0
  161. package/src/types/encore-dsl/cadence.ts +289 -0
  162. package/src/types/encore-dsl/schema.ts +288 -0
  163. package/src/types/notification.ts +2 -1
  164. package/src/types/session.ts +6 -0
  165. package/src/types/sse.ts +5 -0
  166. package/src/types/toolCallHistory.ts +7 -0
  167. package/src/utils/agent/eventDispatch.ts +26 -5
  168. package/src/utils/agent/mcpHint.ts +50 -0
  169. package/src/utils/image/htmlSrcAttrs.ts +117 -13
  170. package/src/utils/session/sessionEntries.ts +8 -32
  171. package/client/assets/PluginScopedRoot-YjvQq0Nn.js +0 -3
  172. package/client/assets/chunk-CernVdwh.js +0 -1
  173. package/client/assets/chunk-D8eiyYIV-CAXpUwLd.js +0 -1
  174. package/client/assets/index-BwrlMMHr.js +0 -5005
  175. package/client/assets/index-CvvNuegU.css +0 -2
  176. package/client/assets/material-symbols-outlined-BOZVWuR3.woff2 +0 -0
  177. package/client/assets/runtime-protocol-vue-C1To4M3t.js +0 -1
  178. package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +0 -4
  179. package/server/api/routes/notifications.ts +0 -195
  180. package/server/notifier/legacy-adapters.ts +0 -76
  181. package/server/workspace/hooks/dispatcher.mjs +0 -300
  182. package/src/composables/useSelectedResult.ts +0 -49
@@ -483,7 +483,16 @@ onUnmounted(() => {
483
483
  .stack-natural :deep(.overflow-x-auto) {
484
484
  overflow: visible !important;
485
485
  }
486
- .stack-natural :deep(.flex-1) {
486
+ /* Scope the flex-1 neutralisation to VERTICAL flex contexts only
487
+ (#1277). The intent was always "don't let a column child's
488
+ flex-1 collapse the natural content height in stack mode". The
489
+ old bare `:deep(.flex-1)` also hit `.flex-1` children of ROW
490
+ containers (e.g. a plugin `<summary>`'s description wrapper),
491
+ freezing them at `flex: 0 0 auto` so they stopped growing /
492
+ wrapping horizontally (PR #1276 had to work around this
493
+ plugin-side). Restricting to `.flex-col > .flex-1` keeps the
494
+ column fix and leaves row layouts alone. */
495
+ .stack-natural :deep(.flex-col > .flex-1) {
487
496
  flex: 0 0 auto !important;
488
497
  }
489
498
  /* presentHtml's View.vue uses CSS-defined (not Tailwind class)
@@ -95,6 +95,7 @@
95
95
  @rename-column="onRenameColumn"
96
96
  @delete-column="onDeleteColumn"
97
97
  @mark-done="onMarkDone"
98
+ @remove-all-items="onRemoveAllItemsInColumn"
98
99
  @reorder-columns="onReorderColumns"
99
100
  />
100
101
  <TodoTableView
@@ -401,6 +402,21 @@ function onMarkDone(columnId: string): void {
401
402
  void patchColumn(columnId, { isDone: true });
402
403
  }
403
404
 
405
+ async function onRemoveAllItemsInColumn(columnId: string): Promise<void> {
406
+ const col = columns.value.find((column) => column.id === columnId);
407
+ if (!col) return;
408
+ // Items without an explicit status render in the first column in
409
+ // the kanban view, so apply the same fallback here.
410
+ const fallbackColumnId = columns.value[0]?.id;
411
+ const idsToDelete = items.value.filter((item) => (item.status ?? fallbackColumnId) === columnId).map((item) => item.id);
412
+ if (idsToDelete.length === 0) return;
413
+ const confirmed = window.confirm(t("todoKanban.removeAllItemsConfirm", { column: col.label, count: idsToDelete.length }));
414
+ if (!confirmed) return;
415
+ for (const itemId of idsToDelete) {
416
+ await deleteItem(itemId);
417
+ }
418
+ }
419
+
404
420
  function onReorderColumns(ids: string[]): void {
405
421
  void reorderColumns(ids);
406
422
  }
@@ -18,7 +18,8 @@
18
18
  starts a column drag. -->
19
19
  <div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 col-handle cursor-grab active:cursor-grabbing">
20
20
  <div class="flex items-center gap-2 min-w-0">
21
- <span class="w-2 h-2 rounded-full shrink-0" :class="col.isDone ? 'bg-green-500' : 'bg-gray-400'" />
21
+ <span v-if="col.isDone" class="material-icons text-base text-gray-700 shrink-0 leading-none" aria-hidden="true">check</span>
22
+ <span v-else class="w-2 h-2 rounded-full shrink-0 bg-gray-400" />
22
23
  <span v-if="renamingId !== col.id" class="font-semibold text-sm text-gray-700 truncate" :title="col.label">{{ col.label }}</span>
23
24
  <input
24
25
  v-else
@@ -31,7 +32,7 @@
31
32
  />
32
33
  <span class="text-xs text-gray-500 shrink-0">{{ itemsByColumn(col.id).length }}</span>
33
34
  </div>
34
- <div class="relative">
35
+ <div class="relative" data-todo-column-menu>
35
36
  <button class="text-gray-400 hover:text-gray-600 px-1" :title="t('todoKanban.columnActions')" @click="toggleMenu(col.id)">
36
37
  <span class="material-icons text-base">more_horiz</span>
37
38
  </button>
@@ -41,10 +42,17 @@
41
42
  @click.stop
42
43
  >
43
44
  <button class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="startRename(col)">{{ t("todoKanban.rename") }}</button>
44
- <button class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="markAsDone(col.id)">
45
- {{ col.isDone ? t("todoKanban.alreadyDoneColumn") : t("todoKanban.markAsDoneColumn") }}
45
+ <button v-if="!col.isDone" class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="markAsDone(col.id)">
46
+ {{ t("todoKanban.markAsDoneColumn") }}
46
47
  </button>
47
- <button class="w-full text-left px-3 py-1.5 text-red-600 hover:bg-red-50" @click="deleteColumn(col.id)">
48
+ <button v-if="col.isDone" class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="removeAllItems(col.id)">
49
+ {{ t("todoKanban.removeAllItems") }}
50
+ </button>
51
+ <button
52
+ class="w-full text-left px-3 py-1.5 text-red-600 hover:bg-red-50 disabled:text-gray-300 disabled:cursor-not-allowed disabled:hover:bg-transparent"
53
+ :disabled="col.isDone"
54
+ @click="deleteColumn(col.id)"
55
+ >
48
56
  {{ t("todoKanban.deleteColumn") }}
49
57
  </button>
50
58
  </div>
@@ -119,7 +127,7 @@
119
127
  </template>
120
128
 
121
129
  <script setup lang="ts">
122
- import { computed, nextTick, ref, watch } from "vue";
130
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
123
131
  import { useI18n } from "vue-i18n";
124
132
  import draggable from "vuedraggable";
125
133
  import type { StatusColumn, TodoItem } from "@mulmoclaude/todo-plugin/shared";
@@ -153,6 +161,7 @@ const emit = defineEmits<{
153
161
  renameColumn: [id: string, label: string];
154
162
  deleteColumn: [id: string];
155
163
  markDone: [id: string];
164
+ removeAllItems: [id: string];
156
165
  reorderColumns: [ids: string[]];
157
166
  }>();
158
167
 
@@ -259,4 +268,23 @@ function markAsDone(columnId: string): void {
259
268
  menuOpenId.value = null;
260
269
  emit("markDone", columnId);
261
270
  }
271
+
272
+ function removeAllItems(columnId: string): void {
273
+ menuOpenId.value = null;
274
+ emit("removeAllItems", columnId);
275
+ }
276
+
277
+ // Close any open column menu when the user clicks outside of its
278
+ // wrapper. We tag each menu wrapper (toggle button + popover) with
279
+ // data-todo-column-menu, so a click on either the toggle or a menu
280
+ // entry is treated as "inside" and won't dismiss; everything else does.
281
+ function onDocumentClickOutsideMenu(event: MouseEvent): void {
282
+ if (menuOpenId.value === null) return;
283
+ const target = event.target as Element | null;
284
+ if (target?.closest("[data-todo-column-menu]")) return;
285
+ menuOpenId.value = null;
286
+ }
287
+
288
+ onMounted(() => document.addEventListener("click", onDocumentClickOutsideMenu));
289
+ onUnmounted(() => document.removeEventListener("click", onDocumentClickOutsideMenu));
262
290
  </script>
@@ -39,7 +39,11 @@ export interface NotifierHistoryEntry extends NotifierEntry {
39
39
  terminalAt: string;
40
40
  }
41
41
 
42
- type NotifierEvent = { type: "published"; entry: NotifierEntry } | { type: "cleared"; id: string } | { type: "cancelled"; id: string };
42
+ type NotifierEvent =
43
+ | { type: "published"; entry: NotifierEntry }
44
+ | { type: "cleared"; id: string }
45
+ | { type: "cancelled"; id: string }
46
+ | { type: "updated"; entry: NotifierEntry };
43
47
 
44
48
  const HISTORY_CAP = 50;
45
49
 
@@ -74,6 +78,22 @@ function applyEvent(event: NotifierEvent): void {
74
78
  entries.value = [...entries.value, event.entry];
75
79
  }
76
80
  return;
81
+ case "updated": {
82
+ // In-place replacement: same id, fresh title/body/severity.
83
+ // No history record — the entry is still active. If the id
84
+ // isn't in our local set (subscribed mid-flight, or the
85
+ // entry was optimistically cleared elsewhere), fall back to
86
+ // append so the bell at least surfaces live state.
87
+ const index = entries.value.findIndex((entry) => entry.id === event.entry.id);
88
+ if (index >= 0) {
89
+ const next = entries.value.slice();
90
+ next[index] = event.entry;
91
+ entries.value = next;
92
+ } else {
93
+ entries.value = [...entries.value, event.entry];
94
+ }
95
+ return;
96
+ }
77
97
  case "cleared":
78
98
  case "cancelled": {
79
99
  const removed = entries.value.find((entry) => entry.id === event.id);
@@ -128,12 +128,6 @@ const HOST_API_ROUTES = {
128
128
  invoke: "/api/mcp-tools/:tool",
129
129
  },
130
130
 
131
- notifications: {
132
- // PoC endpoint for scheduled push fan-out (Web pub-sub + bridge).
133
- // Scaffolding for #144 / #142 — see plans/done/feat-notification-push-scaffold.md.
134
- test: "/api/notifications/test",
135
- },
136
-
137
131
  /** Notifier dispatch — single endpoint, body carries `{ action,
138
132
  * ... }`. Matches the `manage*` tool pattern used elsewhere
139
133
  * (manageEncore / manageAccounting / manageSkills). */
@@ -406,14 +406,19 @@ export const MCP_CATALOG: McpCatalogEntry[] = [
406
406
  displayName: "settingsMcpTab.catalog.entry.github.displayName",
407
407
  description: "settingsMcpTab.catalog.entry.github.description",
408
408
  audience: "general",
409
- upstreamUrl: "https://github.com/modelcontextprotocol/servers/tree/main/src/github",
410
- setupGuideUrl: "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens",
409
+ upstreamUrl: "https://github.com/github/github-mcp-server",
410
+ setupGuideUrl: "https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/set-up-the-github-mcp-server",
411
+ // Switched stdio (@modelcontextprotocol/server-github) → the
412
+ // provider-hosted remote MCP (#1421 A1). HTTP transport works
413
+ // under the Docker sandbox, where the stdio reference server is
414
+ // dropped (server/agent/config.ts — no npx in the minimal
415
+ // image). PAT auth keeps it OAuth-free; the configSchema key
416
+ // and its i18n are unchanged so installs/migrations stay valid.
411
417
  spec: {
412
- type: "stdio",
413
- command: "npx",
414
- args: ["-y", "@modelcontextprotocol/server-github"],
415
- env: {
416
- GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}",
418
+ type: "http",
419
+ url: "https://api.githubcopilot.com/mcp/",
420
+ headers: {
421
+ Authorization: "Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}",
417
422
  },
418
423
  },
419
424
  configSchema: [
@@ -16,6 +16,11 @@ export interface StdioSpec {
16
16
  args?: string[];
17
17
  env?: Record<string, string>;
18
18
  enabled?: boolean;
19
+ /** Opt-in (#1421 Phase B): run on the HOST behind a stdio↔HTTP
20
+ * gateway even in Docker sandbox mode, instead of being dropped.
21
+ * Deliberately escapes the sandbox for this server — the MCP
22
+ * settings UI gates this behind an explicit risk acknowledgment. */
23
+ hostExecInDocker?: boolean;
19
24
  }
20
25
 
21
26
  export type McpServerSpec = HttpSpec | StdioSpec;
@@ -58,21 +58,46 @@ export const ROLES: Role[] = [
58
58
  "- **Browse / lint**: direct the user to the `/wiki` UI — catalog at `/wiki`, a specific page at `/wiki/pages/<slug>`, activity log at `/wiki/log`, or the Lint button on `/wiki` for a health check.\n\n" +
59
59
  "Page format: YAML frontmatter (title, created, updated, tags) + markdown body + `[[wiki links]]` for cross-references. Slugs are lowercase hyphen-separated. Always keep `data/wiki/index.md` current and append to `data/wiki/log.md` after any change. The page-list section of `index.md` is a flat, recency-ordered log: prepend new pages at the top, and when a page is updated (content, description, tags, or rename) move its entry to the top — don't group by category. The Tags section (if present) still needs its per-tag page lists updated on add / rename / delete, but the tag order itself is not reordered by recency. Read `config/helps/wiki.md` for full details.",
60
60
  availablePlugins: [
61
- TOOL_NAMES.manageCalendar,
62
61
  TOOL_NAMES.presentDocument,
63
62
  TOOL_NAMES.presentForm,
64
63
  TOOL_NAMES.presentMulmoScript,
65
64
  TOOL_NAMES.generateImage,
66
65
  TOOL_NAMES.presentHtml,
67
- TOOL_NAMES.mapControl,
68
- TOOL_NAMES.managePhotoLocations,
69
66
  TOOL_NAMES.readXPost,
70
67
  TOOL_NAMES.searchX,
68
+ ],
69
+ queries: [
70
+ "Tell me about this app, MulmoClaude.",
71
+ "What is the wiki in this app and how do I use it?",
72
+ "Tell me about the sandbox feature of this app.",
73
+ "What is the role of the Gemini API key in this app?",
74
+ "How do I use the Telegram bridge to talk to MulmoClaude from my phone?",
75
+ "Show my wiki index",
76
+ "Lint my wiki",
77
+ ],
78
+ },
79
+ {
80
+ id: "personal",
81
+ name: "Personal",
82
+ icon: "person",
83
+ prompt:
84
+ "You are a personal assistant focused on the user's daily life — calendar, todos, recurring obligations (Encore), bookmarks, music, places, and notifications. Help the user organize, track, and recall personal information.\n\n" +
85
+ "## Asking the user to choose\n\n" +
86
+ "When the user must pick from a small set of options, toggle features, or answer yes/no, call presentForm with the appropriate fields (radio for one-of, checkbox for many-of, text/textarea for free-form). Group related questions into one form. Prefer this strongly over phrasing the choice in plain prose — the form gives the user clickable controls and sends the answers back as a markdown bullet list.\n\n" +
87
+ "Mark every field the user must answer as `required: true`. The form blocks submission until required fields are filled, which prevents the LLM from receiving partial responses.",
88
+ availablePlugins: [
89
+ TOOL_NAMES.manageCalendar,
90
+ TOOL_NAMES.defineEncore,
91
+ TOOL_NAMES.manageEncore,
92
+ TOOL_NAMES.managePhotoLocations,
93
+ TOOL_NAMES.mapControl,
71
94
  TOOL_NAMES.notify,
95
+ TOOL_NAMES.presentDocument,
96
+ TOOL_NAMES.presentForm,
72
97
  // Preset runtime plugins (server/plugins/preset-list.ts).
73
98
  // Runtime plugins are gated by `availablePlugins` like the
74
99
  // static-GUI / static-MCP entries above; listed here so the
75
- // out-of-the-box "general" role keeps exposing them. User-
100
+ // out-of-the-box "personal" role keeps exposing them. User-
76
101
  // installed runtime plugins (`~/mulmoclaude/plugins/*`) are
77
102
  // added to roles via Settings → Roles.
78
103
  TOOL_NAMES.manageBookmarks,
@@ -80,15 +105,13 @@ export const ROLES: Role[] = [
80
105
  TOOL_NAMES.manageSpotify,
81
106
  ],
82
107
  queries: [
83
- "Tell me about this app, MulmoClaude.",
84
- "What is the wiki in this app and how do I use it?",
85
- "Tell me about the sandbox feature of this app.",
86
- "What is the role of the Gemini API key in this app?",
87
- "How do I use the Telegram bridge to talk to MulmoClaude from my phone?",
88
- "Show my wiki index",
89
- "Lint my wiki",
90
- "Show my todo list",
91
108
  "Show me my calendar",
109
+ "Show my todo list",
110
+ "What recurring obligations do I have?",
111
+ "Add a bookmark for this URL",
112
+ "Where are the photos I took last weekend?",
113
+ "Play some focus music on Spotify",
114
+ "Remind me to call mom this evening",
92
115
  ],
93
116
  },
94
117
  {
@@ -97,7 +120,7 @@ export const ROLES: Role[] = [
97
120
  icon: "business_center",
98
121
  prompt:
99
122
  "You are a professional office assistant. Create and edit documents, spreadsheets, and presentations. Read existing files in the workspace for context.\n\n" +
100
- "For multi-slide presentations, use presentMulmoScript. Follow the template and rules in config/helps/business.md exactly.\n\n" +
123
+ "For multi-slide presentations, use presentMulmoScript — first Read `config/helps/business.md` for the template and rules, then follow them exactly.\n\n" +
101
124
  "Use presentHtml for rich interactive output such as dashboards, reports with live controls, or data visualizations. Recommended libraries (load via CDN):\n" +
102
125
  "- **UI / layout**: Tailwind CSS — https://cdn.tailwindcss.com\n" +
103
126
  "- **Data visualization**: D3.js — https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js",
@@ -131,7 +154,7 @@ export const ROLES: Role[] = [
131
154
  prompt:
132
155
  "You are a knowledgeable guide and planner. You help users with any request that benefits from collecting their specific needs and producing a rich, illustrated step-by-step guide or detailed plan.\n\n" +
133
156
  "Supported guide types: recipe, travel itinerary, fitness program, event plan, study guide, DIY / home project — or any other scenario where a structured, illustrated document adds value.\n\n" +
134
- "Follow the templates and rules in config/helps/guide.md exactly.\n\n" +
157
+ "Read `config/helps/guide.md` first; follow the templates and rules there exactly.\n\n" +
135
158
  "## Workflow\n\n" +
136
159
  "1. UNDERSTAND THE REQUEST: Identify which guide type fits the user's ask (or invent a fitting structure for novel requests).\n\n" +
137
160
  "2. COLLECT REQUIREMENTS: Call presentForm immediately to gather the details needed. Tailor the form fields to the specific request — see guide.md for per-type field suggestions. Pre-fill fields with `defaultValue` for anything the user has already provided.\n\n" +
@@ -201,7 +224,7 @@ export const ROLES: Role[] = [
201
224
  icon: "auto_stories",
202
225
  prompt:
203
226
  "You are a creative storyteller who crafts vivid, imaginative stories with consistent, named characters across every beat.\n\n" +
204
- "For multi-beat narrated stories, use presentMulmoScript. Follow the template and rules in config/helps/storyteller.md exactly.\n\n" +
227
+ "For multi-beat narrated stories, use presentMulmoScript — first Read `config/helps/storyteller.md` for the template and rules, then follow them exactly.\n\n" +
205
228
  "When asked to create a story:\n" +
206
229
  "1. Decide on 2–5 main characters. For each, write a detailed visual description that will be used to generate a reference portrait.\n" +
207
230
  "2. Define every character in `imageParams.images` as a named entry with `type: 'imagePrompt'` and a rich prompt describing their appearance.\n" +
@@ -310,6 +333,8 @@ export const ROLES: Role[] = [
310
333
  TOOL_NAMES.presentDocument,
311
334
  TOOL_NAMES.presentChart,
312
335
  TOOL_NAMES.presentHtml,
336
+ TOOL_NAMES.readXPost,
337
+ TOOL_NAMES.searchX,
313
338
  ],
314
339
  queries: [
315
340
  "Summarise the key risk factors from AAPL's latest 10-K",
@@ -395,6 +420,7 @@ export const BUILTIN_ROLES = ROLES;
395
420
  // updating this map fails the test.
396
421
  export const BUILTIN_ROLE_IDS = {
397
422
  general: "general",
423
+ personal: "personal",
398
424
  office: "office",
399
425
  guide: "guide",
400
426
  artist: "artist",
@@ -414,6 +440,17 @@ export type BuiltInRoleId = (typeof BUILTIN_ROLE_IDS)[keyof typeof BUILTIN_ROLE_
414
440
 
415
441
  export const DEFAULT_ROLE_ID: BuiltInRoleId = BUILTIN_ROLE_IDS.general;
416
442
 
443
+ // Role id that Encore's `resolveNotification` flow seeds new chats
444
+ // with. Pinned to `personal` because that role owns the Encore tool
445
+ // pair (`defineEncore` for DSL composition / amendment,
446
+ // `manageEncore` for operational kinds like markStepDone / snooze).
447
+ // Seeding into a role without these tools leaves the agent unable
448
+ // to drive the obligation it was just woken up for. Guarded by
449
+ // `test/roles/test_encore_seed_role.ts` so renaming the role or
450
+ // dropping either tool from its `availablePlugins` fails CI rather
451
+ // than silently breaking the seeded chat.
452
+ export const ENCORE_SEED_ROLE_ID: BuiltInRoleId = BUILTIN_ROLE_IDS.personal;
453
+
417
454
  export function getRole(roleId: string): Role {
418
455
  return ROLES.find((role) => role.id === roleId) ?? ROLES[0];
419
456
  }
@@ -155,6 +155,18 @@ export function descriptorForPath(filePath: string): SystemFileDescriptor | null
155
155
  return null;
156
156
  }
157
157
 
158
+ // editPolicy values for which the Files Explorer offers an inline
159
+ // JSON editor (#833 Phase 1). A path with no descriptor is a plain
160
+ // user-owned file → editable. `agent-managed` / `fragile-format` /
161
+ // `ephemeral` are withheld: hand-edits there risk corrupting state the
162
+ // agent or app owns, or a format too brittle for free-text editing.
163
+ const JSON_EDITABLE_POLICIES: ReadonlySet<EditPolicy> = new Set<EditPolicy>(["user-editable", "agent-managed-but-hand-editable"]);
164
+
165
+ export function jsonEditableByPolicy(filePath: string): boolean {
166
+ const descriptor = descriptorForPath(filePath);
167
+ return descriptor === null || JSON_EDITABLE_POLICIES.has(descriptor.editPolicy);
168
+ }
169
+
158
170
  // Tailwind text colors used to tint a file-icon (or any single-color
159
171
  // glyph) according to the system file's edit policy. Same hue as the
160
172
  // banner's chip but text-only (no background) — the chip uses a
package/src/lang/de.ts CHANGED
@@ -28,7 +28,7 @@ const deMessages = {
28
28
  },
29
29
  },
30
30
  chatInput: {
31
- placeholder: "Aufgabe eingeben oder Datei ziehen / einfügen / anhängen…",
31
+ placeholder: "Nachricht an Claude…",
32
32
  send: "Senden",
33
33
  attachFile: "Datei anhängen",
34
34
  fileTooLarge: "Datei zu groß ({sizeMB} MB). Das Maximum beträgt 30 MB.",
@@ -68,6 +68,8 @@ const deMessages = {
68
68
  clearAll: "Alle löschen",
69
69
  dismiss: "Verwerfen",
70
70
  cancel: "Abbrechen",
71
+ showMore: "Mehr anzeigen ({count})",
72
+ showLess: "Weniger anzeigen",
71
73
  },
72
74
  pluginDiagnostics: {
73
75
  title: "Plugin-Konfigurationsproblem",
@@ -76,6 +78,18 @@ const deMessages = {
76
78
  intraBody:
77
79
  "Die Plugins „{first}“ und „{second}“ registrieren beide {dimension} „{key}“. „{first}“ hat ihn zuerst beansprucht, daher wird die Registrierung von „{second}“ ignoriert.",
78
80
  },
81
+ optionalDeps: {
82
+ // Generischer `title` aus Abwärtskompatibilität für persistierte
83
+ // History-Einträge vor der Aufteilung nach Grund; neue Pfade
84
+ // verwenden `titleNotFound` / `titleNotResponding`.
85
+ title: "Optionale Abhängigkeit nicht verfügbar",
86
+ titleNotFound: "{command} ist nicht installiert",
87
+ titleNotResponding: "{command} läuft nicht",
88
+ notFound:
89
+ "{command} nicht gefunden — zugehörige Funktionen wurden deaktiviert. Installieren Sie {command} und starten Sie MulmoClaude neu, um sie zu aktivieren.",
90
+ notResponding:
91
+ "{command} ist installiert, läuft aber nicht — zugehörige Funktionen wurden deaktiviert. Starten Sie {command} und starten Sie MulmoClaude neu, um sie zu aktivieren.",
92
+ },
79
93
  pluginErrorBoundary: {
80
94
  title: "Plugin {pkg} ist abgestürzt",
81
95
  subtitle: "Das Plugin konnte nicht gerendert werden. Der Fehler wurde in der Konsole protokolliert.",
@@ -105,6 +119,11 @@ const deMessages = {
105
119
  error: "Fehler",
106
120
  result: "Ergebnis",
107
121
  running: "Läuft...",
122
+ mcpHint: {
123
+ title: (ctx: { named: (key: "server") => string }) => `Einrichtungshinweis: ${ctx.named("server")}`,
124
+ requiredKeys: "Erforderliche Schlüssel",
125
+ setupGuide: "Einrichtungsanleitung öffnen",
126
+ },
108
127
  },
109
128
  fileTreePane: {
110
129
  sort: "Sortieren:",
@@ -141,6 +160,7 @@ const deMessages = {
141
160
  },
142
161
  settingsModal: {
143
162
  title: "Einstellungen",
163
+ version: "MulmoClaude v{version}",
144
164
  tabs: {
145
165
  gemini: "Gemini-API-Schlüssel",
146
166
  tools: "Erlaubte Tools",
@@ -254,15 +274,43 @@ const deMessages = {
254
274
  errLabelConflict: 'Label "{label}" existiert bereits',
255
275
  },
256
276
  pluginLauncher: {
257
- todos: { label: "To-dos", title: "To-dos öffnen" },
258
- calendar: { label: "Kalender", title: "Kalender öffnen" },
259
- automations: { label: "Aktionen", title: "Aktionen öffnen" },
260
- wiki: { label: "Wiki", title: "Wiki öffnen" },
261
- sources: { label: "Quellen", title: "Informationsquellen öffnen" },
262
- news: { label: "Nachrichten", title: "Nachrichten-Viewer öffnen" },
263
- skills: { label: "Skills", title: "Skills öffnen" },
264
- roles: { label: "Rollen", title: "Rollen öffnen" },
265
- files: { label: "Dateien", title: "Workspace-Dateien öffnen" },
277
+ todos: { label: "To-dos" },
278
+ calendar: { label: "Kalender" },
279
+ automations: { label: "Aktionen" },
280
+ encore: { label: "Encore" },
281
+ wiki: { label: "Wiki" },
282
+ sources: { label: "Quellen" },
283
+ news: { label: "Nachrichten" },
284
+ skills: { label: "Skills" },
285
+ roles: { label: "Rollen" },
286
+ files: { label: "Dateien" },
287
+ },
288
+ encoreDashboard: {
289
+ title: "Encore",
290
+ subtitle: "Wiederkehrende Verpflichtungen, die Encore verfolgt.",
291
+ loading: "Verpflichtungen werden geladen…",
292
+ errorPrefix: "Verpflichtungen konnten nicht geladen werden: ",
293
+ empty: "Noch keine Verpflichtungen. Frage im Chat nach, um eine einzurichten.",
294
+ noCycles: "Noch keine Zyklen erfasst.",
295
+ cyclesSuffix: "Zyklen",
296
+ targetCount: "{count} Ziel | {count} Ziele",
297
+ cycleClosed: "Erledigt",
298
+ chatButtonTitle: "Diese Verpflichtung in einem neuen Chat besprechen",
299
+ bellButtonTitle: "Diesen Zyklus besprechen",
300
+ addButtonLabel: "Hinzufügen",
301
+ unexpectedResponse: "Encore hat eine unerwartete Antwort zurückgegeben.",
302
+ status: {
303
+ active: "Aktiv",
304
+ paused: "Pausiert",
305
+ retired: "Beendet",
306
+ },
307
+ cadence: {
308
+ daily: "Täglich",
309
+ weekly: "Wöchentlich",
310
+ monthly: "Monatlich",
311
+ biannual: "Halbjährlich",
312
+ annual: "Jährlich",
313
+ },
266
314
  },
267
315
  fileContentHeader: {
268
316
  showRendered: "Gerendertes Markdown anzeigen",
@@ -276,6 +324,11 @@ const deMessages = {
276
324
  htmlPreview: "HTML-Vorschau",
277
325
  pdfPreview: "PDF-Vorschau",
278
326
  parseError: "Parse-Fehler",
327
+ editJson: "JSON bearbeiten",
328
+ jsonEditorLabel: "JSON-Editor",
329
+ invalidJson: "Ungültiges JSON",
330
+ undo: "Rückgängig",
331
+ redo: "Wiederholen",
279
332
  },
280
333
  filesView: {
281
334
  chatPlaceholder: "Frage zu dieser Datei stellen…",
@@ -396,6 +449,9 @@ const deMessages = {
396
449
  urlLabel: "URL:",
397
450
  commandLabel: "Befehl:",
398
451
  dockerStdioUnsupported: "⚠ Wird nicht ausgeführt, solange die Docker-Sandbox aktiv ist.",
452
+ dockerStdioHostExecActive: "⚠ Läuft auf dem Host — dieser Server verlässt die Docker-Sandbox.",
453
+ dockerStdioHostExecOptIn:
454
+ "Trotzdem auf dem Host ausführen (fortgeschritten). Dieser Server läuft über ein lokales HTTP-Gateway außerhalb der Docker-Sandbox und kann auf Ihren Rechner zugreifen.",
399
455
  learnMore: "Mehr erfahren",
400
456
  addServerButton: "+ MCP-Server hinzufügen",
401
457
  nameLabel: "Name",
@@ -579,6 +635,7 @@ const deMessages = {
579
635
  yamlParseError: "YAML konnte nicht geparst werden — stellen Sie sicher, dass 'title' vorhanden ist",
580
636
  propLabel: "{key}:",
581
637
  moreCount: "+{count} weitere",
638
+ invalidRange: "Ungültiger Datumsbereich (endDate: {endDate})",
582
639
  previewIcon: "📅",
583
640
  previewUpcoming: "{count} anstehend",
584
641
  previewAutomations: "{count} Automatisierung | {count} Automatisierungen",
@@ -871,8 +928,9 @@ const deMessages = {
871
928
  },
872
929
  todoKanban: {
873
930
  rename: "Umbenennen",
874
- alreadyDoneColumn: 'Bereits Spalte „Erledigt"',
875
- markAsDoneColumn: 'Als Spalte „Erledigt" markieren',
931
+ markAsDoneColumn: "Als Spalte „Erledigt“ markieren",
932
+ removeAllItems: "Alle Elemente entfernen",
933
+ removeAllItemsConfirm: "Alle {count} Elemente in „{column}“ entfernen? Diese Aktion kann nicht rückgängig gemacht werden.",
876
934
  deleteColumn: "Spalte löschen",
877
935
  columnActions: "Spaltenaktionen",
878
936
  addCard: "+ Karte hinzufügen",
@@ -1089,6 +1147,42 @@ const deMessages = {
1089
1147
  errSaveFailed: "Speichern fehlgeschlagen: {error}",
1090
1148
  errDeleteFailed: "Löschen fehlgeschlagen",
1091
1149
  confirmDelete: 'Skill "{name}" löschen? Dies entfernt ~/mulmoclaude/.claude/skills/{name}/SKILL.md.',
1150
+ sectionActive: "Aktiv",
1151
+ sectionCatalog: "Katalog",
1152
+ sectionLegend:
1153
+ "Aktiv: in den Prompt geladen ({system} System = mc- mitgeliefert · {project} Projekt = bearbeitbar · {user} Benutzer = ~/.claude/skills/). Katalog: durchsuchen, ★markieren oder ▶einmal ausführen, ohne den Prompt aufzublähen.",
1154
+ catalogEmpty: "Keine Preset-Skills verfügbar.",
1155
+ catalogPresetHeading: "Presets",
1156
+ catalogStar: "Markieren",
1157
+ catalogStarred: "Markiert",
1158
+ catalogRunOnce: "Einmalig ausführen",
1159
+ sourceUserTitle: "Benutzer-Skill (~/.claude/skills/, in allen Workspaces verfügbar)",
1160
+ sourceSystemTitle: "System-Skill (mitgeliefert, mc- Präfix — schreibgeschützt, vom Launcher überschrieben)",
1161
+ sourceProjectTitle: "Projekt-Skill (.claude/skills/ des Workspaces, nur dieser Workspace)",
1162
+ sourcePresetTitle: "Preset-Katalog — Markieren anklicken, um in diesem Workspace zu aktivieren",
1163
+ errCatalogListFailed: "Katalog konnte nicht geladen werden: {error}",
1164
+ errCatalogStarFailed: "Skill konnte nicht markiert werden: {error}",
1165
+ errCatalogPreviewFailed: "Skill-Vorschau konnte nicht geladen werden: {error}",
1166
+ errCatalogRunOnceEmpty: "Diese Skill hat keinen Inhalt zum Ausführen.",
1167
+ catalogAddRepo: "Skill-Repository hinzufügen",
1168
+ catalogAddRepoTitle: "Ein Skill-Repository hinzufügen",
1169
+ catalogRepoUrlLabel: "GitHub-URL",
1170
+ catalogRepoUrlPlaceholder: "https://github.com/owner/repo",
1171
+ catalogRepoSubpathLabel: "Unterpfad (optional)",
1172
+ catalogRepoSubpathPlaceholder: "skills",
1173
+ catalogAddRepoSubmit: "Installieren",
1174
+ catalogAddRepoSuggestions: "Vorgeschlagene Repositories",
1175
+ catalogUninstallRepo: "Repository deinstallieren",
1176
+ catalogUpdateRepo: "Repository aktualisieren (neueste Version erneut laden)",
1177
+ catalogRepoOpenLink: "Repository auf GitHub öffnen (neuer Tab)",
1178
+ catalogUninstallConfirm: "Dieses Repository deinstallieren? Bereits mit Stern markierte Skills bleiben in deiner aktiven Liste.",
1179
+ catalogRepoInstalling: "Installiere…",
1180
+ catalogRepoEmpty: "In diesem Repository wurden keine Skills gefunden.",
1181
+ sourceExternalTitle: "Externe Skill (aus einem GitHub-Repository installiert — zum Aktivieren auf den Stern klicken)",
1182
+ errCatalogRepoListFailed: "Installierte Repositories konnten nicht geladen werden: {error}",
1183
+ errCatalogRepoInstallFailed: "Repository konnte nicht installiert werden: {error}",
1184
+ errCatalogRepoUninstallFailed: "Repository konnte nicht deinstalliert werden: {error}",
1185
+ errCatalogRepoInvalidUrl: "Gib eine GitHub-Repository-URL ein.",
1092
1186
  },
1093
1187
  pluginManageRoles: {
1094
1188
  heading: "Benutzerdefinierte Rollen",
@@ -1143,6 +1237,8 @@ const deMessages = {
1143
1237
  stop: "■ Stoppen",
1144
1238
  playPresentation: "Präsentation abspielen",
1145
1239
  regenerateMovie: "Video neu generieren",
1240
+ movieGenerationFailed: "Videoerstellung fehlgeschlagen",
1241
+ retry: "Erneut versuchen",
1146
1242
  errPrefix: "⚠ Fehler",
1147
1243
  noBeats: "Keine Beats im Skript gefunden",
1148
1244
  editSource: "Skript-Quelle bearbeiten",