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
@@ -0,0 +1,482 @@
1
+ # Encore — recurring obligations DSL
2
+
3
+ Encore tracks recurring obligations (monthly payments, biannual taxes, annual physicals, daily check-ins) defined in a small YAML DSL. You — the LLM — compose the DSL document when the user describes an obligation, and call `defineEncore({ dsl })` to store it.
4
+
5
+ Encore then:
6
+ - Fires bell notifications at the right times based on `firingPlan` phases.
7
+ - Bundles multi-target notifications into one bell entry (e.g. Isamu + Singularity Society both monthly, same deadline → one ding).
8
+ - Escalates severity over time as deadlines approach.
9
+ - Defers chat creation until the user clicks the bell — when they do, they land in a fresh chat with you, seeded with a prompt that names the obligation, the open targets, and the `pendingId` to pass back to `markStepDone` (or `markTargetSkipped` / `snooze`) for clearing.
10
+
11
+ You never call `chat.start` directly for an obligation. The bell click handles that. You just close the loop on the resulting chat by calling `markStepDone` with the `pendingId` from the seed prompt.
12
+
13
+ ## Your end-to-end loop
14
+
15
+ 1. User describes a recurring obligation → you compose a DSL document → call `defineEncore({ dsl })` (no `obligationId` → setup).
16
+ 2. Encore fires bell notifications at the right times. You do NOT call `chat.start` — the host opens a fresh chat with you when the user clicks the bell, and seeds it with a prompt that names the obligation, the open targets, and a `pendingId`.
17
+ 3. In that seeded chat: converse with the user about the obligation, collect what they recorded, and call the matching action (`markStepDone` / `markTargetSkipped` / `recordValues` / `snooze`) passing the `pendingId`. That's the ONLY way the bell entry clears — there is no separate clear/dismiss action.
18
+ 4. Encore handles cycle recurrence: closing a cycle (all targets done or skipped) provisions the next cycle on the next tick. You don't need to do anything for that.
19
+
20
+ ## When to use this
21
+
22
+ Whenever the user describes something that recurs and they want to be reminded about:
23
+ - "I pay rent every month, due on the 1st" → monthly payment obligation
24
+ - "Property tax is due twice a year on April 30 and November 30" → biannual payment
25
+ - "I get an annual physical in May; I need to make the appointment in April" → annual service, two steps
26
+ - "Take vitamins every day" → daily service
27
+ - "Pay Isamu and Singularity Society both monthly on the 10th" → ONE obligation, TWO targets
28
+
29
+ Compose the full DSL, then call `defineEncore({ dsl })` (no `obligationId` → setup path). Encore generates `id` (slugified from `displayName`) and `createdAt` server-side. To change an existing obligation later, call `defineEncore({ obligationId, dsl: { /* fields to change */ } })` — that's the amend path.
30
+
31
+ ## Top-level DSL shape
32
+
33
+ ```yaml
34
+ version: 1
35
+ displayName: "Daily payment — Hisayo"
36
+ status: active # active | paused | retired (default: active)
37
+
38
+ type: payment # payment | service
39
+ currency: JPY # ISO 4217, REQUIRED iff type=payment, FORBIDDEN otherwise
40
+
41
+ cadence:
42
+ type: daily # see Cadence below
43
+
44
+ targets: # length >= 1
45
+ - id: hisayo # kebab-case slug, unique within obligation
46
+ displayName: "Hisayo"
47
+ defaults: # optional; pre-fills field values per cycle
48
+ method: Cash
49
+
50
+ steps: # length >= 1
51
+ - id: pay # kebab-case slug, unique within obligation
52
+ displayName: "Pay"
53
+ deadline: cycle-deadline # at-expression (see below)
54
+ firingPlan:
55
+ - at: cycle-start # at-expression
56
+ severity: info # info | warning | urgent
57
+ fields: [amount, method, paidOn] # subset of formSchema field names
58
+
59
+ formSchema: # input grammar for per-cycle values
60
+ fields:
61
+ - name: amount # camelCase or kebab-case
62
+ type: number # string | text | url | email | date | number | boolean | enum
63
+ label: "Amount paid (JPY)"
64
+ required: true
65
+ - name: method
66
+ type: string
67
+ label: "Payment method"
68
+ - name: paidOn
69
+ type: date
70
+ label: "Payment date"
71
+
72
+ carryForward: # optional
73
+ body: empty # empty | copy (default: empty)
74
+ ```
75
+
76
+ Cross-field rules (validator will reject otherwise):
77
+ - `currency` required iff `type` is `payment`.
78
+ - `targets[].id`, `steps[].id`, `formSchema.fields[].name` are each unique within their list.
79
+ - Every `formSchema` field name must be claimed by **exactly one** step's `fields[]` — no orphans, no double-claims.
80
+ - `targets[].defaults` keys must reference real `formSchema` field names.
81
+ - `firingPlan` phases must resolve in chronological order (Encore evaluates them in declared order).
82
+ - `defineEncore` amend cannot change `type`, `currency`, or `cadence.type` — those changes invalidate cycle-file naming or prior records. Path: retire + create new.
83
+
84
+ ## Cadence
85
+
86
+ ```yaml
87
+ cadence: { type: annual, cycles: [{ month: 5, day: 28 }] }
88
+ cadence: { type: biannual, cycles: [{ month: 4, day: 28 }, { month: 11, day: 28 }] }
89
+ cadence: { type: monthly, day: 10 }
90
+ cadence: { type: weekly, dayOfWeek: friday } # mon|tue|wed|thu|fri|sat|sun
91
+ cadence: { type: daily }
92
+ ```
93
+
94
+ - `month` is 1–12, `day` is 1–28 (capped to dodge February).
95
+ - `biannual` cycles must be in calendar order (first slot before second).
96
+ - Cycle file naming: `<year>.md` (annual), `<year>-h{1,2}.md` (biannual), `<year>-MM.md` (monthly), `<year>-Www.md` (weekly), `YYYY-MM-DD.md` (daily).
97
+
98
+ ## `at` expressions (firingPlan & step.deadline)
99
+
100
+ Grammar: `anchor [±N d]` where anchor is one of:
101
+ - `cycle-start` — first day of the cycle
102
+ - `cycle-deadline` — the cycle's natural deadline
103
+ - `step-deadline` — this step's resolved deadline (ONLY valid inside the same step's `firingPlan`)
104
+ - `schedule:YYYY-MM-DD` — absolute date
105
+
106
+ Examples:
107
+ - `cycle-start` — fire on day 1
108
+ - `cycle-deadline-21d` — three weeks before deadline
109
+ - `step-deadline+1d` — one day after the step's deadline (e.g. for an "overdue" escalation)
110
+ - `schedule:2026-02-01` — absolute
111
+
112
+ Days only — no `w` / `m`. Compute the math yourself.
113
+
114
+ ## Severity
115
+
116
+ Three severities: `info`, `warning`, `urgent`. They drive the bell's visual prominence and the escalation log; the LLM also reads severity from the seed prompt and adjusts tone in conversation. Phases must be chronologically ordered but severities can be non-monotonic (rare).
117
+
118
+ ## Two MCP tools — defineEncore vs manageEncore
119
+
120
+ Encore exposes two MCP tools that share the same `/api/encore` endpoint:
121
+
122
+ - **`defineEncore`** — compose a new DSL document, or amend an existing one. Use this for ANY structural change (creating an obligation, renaming it, changing the firingPlan, adding a target, etc.).
123
+ - **`manageEncore`** — operational kinds only: `markStepDone` / `markTargetSkipped` / `recordValues` / `query` / `appendNote` / `snooze` / `unsnooze`. Use these after the obligation exists, typically in the chat seeded by a bell click.
124
+
125
+ The split exists so the `defineEncore` tool can carry a fully typed JSON Schema for the `dsl` argument (you'll see field names, types, and oneOf branches in the tool definition), while `manageEncore` stays a thin discriminator on short flat arguments.
126
+
127
+ ## defineEncore — setup or amend
128
+
129
+ Discriminator: **`obligationId` presence**.
130
+
131
+ - Absent → setup (server generates the id from `displayName`).
132
+ - Present → amend the named obligation.
133
+
134
+ This way the parameter shape carries the intent — no separate `kind: "setup" | "amend"` flag inside the tool.
135
+
136
+ ### setup — create a new obligation
137
+
138
+ ```json
139
+ {
140
+ "kind": "defineEncore",
141
+ "dsl": {
142
+ "version": 1,
143
+ "displayName": "Daily check-in",
144
+ "type": "service",
145
+ "cadence": { "type": "daily" },
146
+ "targets": [{ "id": "me", "displayName": "Me" }],
147
+ "steps": [
148
+ {
149
+ "id": "checkin",
150
+ "displayName": "Check in",
151
+ "deadline": "cycle-deadline",
152
+ "firingPlan": [{ "at": "cycle-start", "severity": "info" }],
153
+ "fields": ["note"]
154
+ }
155
+ ],
156
+ "formSchema": {
157
+ "fields": [
158
+ { "name": "note", "type": "text", "label": "Notes", "required": false }
159
+ ]
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ Returns `{ ok: true, obligationId, cycleId, cyclePath, indexPath }`. Encore writes `obligations/<id>/index.md` plus the first cycle file and reconciles (so a `cycle-start` phase fires immediately).
166
+
167
+ `dsl` is normally an **OBJECT** in the tool-call arguments. The handler also accepts a JSON-encoded string (it calls `JSON.parse` on the string before validating) so a `JSON.stringify`'d dsl won't error — but the object form is preferred.
168
+
169
+ #### Obligation with nothing to record (placeholder field)
170
+
171
+ The DSL requires **every obligation to have at least one formSchema field, and every formSchema field to be claimed by exactly one step.** For a "did I do it?" obligation that captures no real data, declare a single placeholder field (typical names: `note`, `done`, `time`) and claim it from your only step — exactly as the example above does.
172
+
173
+ You **cannot** combine `step.fields: []` with `formSchema.fields: []` to opt out — `formSchema.fields` has `.min(1)` and `formSchema` is required. The LLM-trap to avoid:
174
+
175
+ ```json
176
+ // ❌ FAILS: orphan field
177
+ "steps": [{ "id": "shower", "fields": [], ... }],
178
+ "formSchema": { "fields": [{ "name": "note", "type": "text", "label": "Notes" }] }
179
+
180
+ // ❌ FAILS: empty array (formSchema.fields requires ≥1)
181
+ "steps": [{ "id": "shower", "fields": [], ... }],
182
+ "formSchema": { "fields": [] }
183
+
184
+ // ✅ WORKS: placeholder claimed by the step
185
+ "steps": [{ "id": "shower", "fields": ["note"], ... }],
186
+ "formSchema": { "fields": [{ "name": "note", "type": "text", "label": "Notes", "required": false } ] }
187
+ ```
188
+
189
+ If you hit "field X is not claimed by any step.fields[]", the fix is to **add** the field name to one step's `fields[]`, NOT to remove it from `formSchema`.
190
+
191
+ ### setup — 409 collision behavior
192
+
193
+ If `slugify(dsl.displayName)` matches an existing obligation, the server rejects with `409 Conflict` and a recovery directive:
194
+
195
+ > Obligation "daily-payment-hisayo" already exists (displayName: "Daily payment — Hisayo"). To modify it, call defineEncore with obligationId: "daily-payment-hisayo" (this becomes an amend). To create a parallel obligation, change the displayName.
196
+
197
+ Read the message — it tells you the id to pass for amend. Don't auto-disambiguate by appending suffixes; the user almost always wants one of: (a) you forgot `obligationId` and meant amend, or (b) you genuinely want a new obligation under a different name.
198
+
199
+ ### amend — change one or more fields
200
+
201
+ ```json
202
+ {
203
+ "kind": "defineEncore",
204
+ "obligationId": "daily-payment-hisayo",
205
+ "dsl": { "displayName": "Daily payment — Hisayo San" }
206
+ }
207
+ ```
208
+
209
+ For amend, only fill the fields you want to change — the server shallow-merges onto the existing DSL. Array fields (`targets`, `steps`, `formSchema.fields`, `firingPlan`) replace whole; if you want to add a new step, send the full new `steps` array (existing + new).
210
+
211
+ Cannot change `type` / `currency` / `cadence.type` — those are immutable. Path: retire the old obligation, create a new one.
212
+
213
+ Encore clears active bell entries on amend and re-fires with the new title/text.
214
+
215
+ #### Merge semantics — what "shallow merge at the top level" means
216
+
217
+ Each top-level key you include is OVERWRITTEN whole on the stored DSL. Keys you omit are PRESERVED. There is no per-field merge inside an object or array — you're either replacing the whole top-level value or leaving it alone.
218
+
219
+ | Top-level key | Type | What "amend it" means |
220
+ |---|---|---|
221
+ | `displayName`, `status`, `currency` (read-only via amend) | scalar | Replaced by the value you send. |
222
+ | `cadence` | object | Replaced whole. Re-send all required cadence fields, except `cadence.type` (immutable). |
223
+ | `targets` | array | Replaced whole. Send the FULL desired list — old entries you omit are gone. |
224
+ | `steps` | array | Replaced whole. Same rule — old steps you omit are gone, and their per-step `firingPlan` goes with them. |
225
+ | `formSchema` | object | Replaced whole. `formSchema.fields` (array) is part of that. |
226
+
227
+ Note that there is NO deep-merge inside `targets[i]`, `steps[i]`, or `firingPlan[i]`. To change one step's `firingPlan`, you re-send the whole step (including the unchanged fields), inside the full `steps` array (including the unchanged steps).
228
+
229
+ #### Worked example — add a target without losing the existing ones
230
+
231
+ Existing DSL has `targets: [hisayo, kenta]`. To add `mei`, send the full new list:
232
+
233
+ ```json
234
+ {
235
+ "kind": "defineEncore",
236
+ "obligationId": "daily-payment-hisayo",
237
+ "dsl": {
238
+ "targets": [
239
+ { "id": "hisayo", "displayName": "Hisayo" },
240
+ { "id": "kenta", "displayName": "Kenta" },
241
+ { "id": "mei", "displayName": "Mei" }
242
+ ]
243
+ }
244
+ }
245
+ ```
246
+
247
+ Sending only `{ targets: [{ id: "mei", ... }] }` would DROP hisayo and kenta — the array replaces whole, it does not append.
248
+
249
+ #### Worked example — change one step's firingPlan without touching others
250
+
251
+ Existing DSL has `steps: [pay, confirm]`, and you want to change `pay.firingPlan` only. Re-send the full steps array, with the full `pay` step (including the new `firingPlan`) and the full unchanged `confirm` step:
252
+
253
+ ```json
254
+ {
255
+ "kind": "defineEncore",
256
+ "obligationId": "daily-payment-hisayo",
257
+ "dsl": {
258
+ "steps": [
259
+ {
260
+ "id": "pay",
261
+ "displayName": "Pay",
262
+ "deadline": "cycle-deadline",
263
+ "fields": ["amount"],
264
+ "firingPlan": [
265
+ { "at": "cycle-deadline-3d", "severity": "info" },
266
+ { "at": "cycle-deadline", "severity": "urgent" }
267
+ ]
268
+ },
269
+ {
270
+ "id": "confirm",
271
+ "displayName": "Confirm receipt",
272
+ "deadline": "cycle-deadline+1d",
273
+ "fields": [],
274
+ "firingPlan": [{ "at": "cycle-deadline+1d", "severity": "info" }]
275
+ }
276
+ ]
277
+ }
278
+ }
279
+ ```
280
+
281
+ You CANNOT send `{ steps: [{ id: "pay", firingPlan: [...] }] }` and expect the server to find the `pay` step and patch only its `firingPlan` — the whole `steps` array replaces, and `confirm` would be lost.
282
+
283
+ #### Worked example — partial cadence update
284
+
285
+ `cadence.type` is immutable, but its sibling fields (e.g. `day` on monthly, `dayOfWeek` on weekly, the `cycles` list on annual / biannual) are amendable. Re-send the full cadence object with the new value plus the unchanged `type`:
286
+
287
+ ```json
288
+ {
289
+ "kind": "defineEncore",
290
+ "obligationId": "monthly-rent",
291
+ "dsl": { "cadence": { "type": "monthly", "day": 5 } }
292
+ }
293
+ ```
294
+
295
+ Sending `{ cadence: { day: 5 } }` (without `type`) will 400 with a Zod error — cadence is replaced whole, so the required discriminator field must be present.
296
+
297
+ #### When NOT to use amend
298
+
299
+ `type`, `currency` (for `type: "payment"`), and `cadence.type` are immutable — amend will 400. The path is retire-and-create: set `status: "retired"` (or `"paused"`) on the old obligation, then `defineEncore` (no `obligationId`) a new one with the desired type / currency / cadence-type. Bells on the old obligation clear automatically when status leaves `active`.
300
+
301
+ ## manageEncore call shapes
302
+
303
+ Every operational action takes a `kind` discriminator. The handler validates the rest with Zod and 400s on shape mistakes — read the error message; it names the field.
304
+
305
+ For composing a NEW obligation or amending an existing one's DSL, use the sibling `defineEncore` tool documented above — that's structural, not operational, and lives outside `manageEncore`.
306
+
307
+ ### markStepDone — CLOSE ONE STEP ON ONE TARGET
308
+
309
+ ```json
310
+ {
311
+ "kind": "markStepDone",
312
+ "pendingId": "<from the seed prompt>",
313
+ "obligationId": "daily-payment-hisayo",
314
+ "cycleId": "2026-05-16",
315
+ "targetId": "hisayo",
316
+ "stepId": "pay",
317
+ "values": { "amount": 5000, "paidOn": "2026-05-16" }
318
+ }
319
+ ```
320
+
321
+ **Common mistakes the parser will 400 on:**
322
+ - `targetIds: ["hisayo"]` — WRONG. Use singular `targetId: "hisayo"` (string). If the bell covered multiple targets, call `markStepDone` once per target.
323
+ - `values: { "hisayo": { "amount": 5000 } }` — WRONG. `values` is a flat field-map keyed by field name: `{ "amount": 5000, "paidOn": "..." }`. Never nest under target id.
324
+ - Missing `pendingId` when called in response to a bell click — the bell entry won't clear without it.
325
+
326
+ ### markTargetSkipped
327
+
328
+ ```json
329
+ { "kind": "markTargetSkipped", "pendingId": "...", "obligationId": "...", "cycleId": "...", "targetId": "hisayo" }
330
+ ```
331
+
332
+ Marks one target skipped for this cycle. Remaining targets still need their own close.
333
+
334
+ ### recordValues
335
+
336
+ ```json
337
+ { "kind": "recordValues", "obligationId": "...", "cycleId": "...", "targetId": "...", "values": { "amount": 5000 } }
338
+ ```
339
+
340
+ Writes partial values without closing the step. Use when the user has reported partial info (invoice received but not yet paid).
341
+
342
+ ### snooze
343
+
344
+ ```json
345
+ { "kind": "snooze", "pendingId": "...", "obligationId": "...", "cycleId": "...", "targetId": "...", "stepId": "..." }
346
+ ```
347
+
348
+ Clears the current bell entry and persists a `snoozedSteps[stepId]` marker (24h by default) on the cycle file. The reconciler skips this step until the snooze timestamp passes; after that, the next reconcile re-fires from the current phase.
349
+
350
+ ### unsnooze
351
+
352
+ ```json
353
+ { "kind": "unsnooze", "obligationId": "...", "cycleId": "...", "targetId": "...", "stepId": "..." }
354
+ ```
355
+
356
+ Inverse of `snooze`. Deletes `snoozedSteps[stepId]` from the target's record. If the step is otherwise eligible to fire (not closed, current phase past), the bell republishes in the same turn — no need to wait for the 24h timer or run a tick manually. A no-op if the step wasn't snoozed.
357
+
358
+ ### query
359
+
360
+ ```json
361
+ { "kind": "query", "obligationId": "daily-payment-hisayo", "range": "all" }
362
+ ```
363
+
364
+ `range` is `"current"` (default, last cycle only), `"all"` (every cycle ever), or a number (last N cycles). Omit `obligationId` to query every obligation. Response includes each cycle's `path` so you can deep-read the raw markdown for further analysis.
365
+
366
+ ### appendNote
367
+
368
+ ```json
369
+ { "kind": "appendNote", "obligationId": "...", "cycleId": "2026-05-16", "body": "Note appended here" }
370
+ ```
371
+
372
+ Omit `cycleId` to append to the obligation's index.md body (long-lived notes); include it to append to a specific cycle's body (per-cycle scratch).
373
+
374
+ ## Three worked examples
375
+
376
+ ### 1. Monthly payments, two targets, bundled notification
377
+
378
+ ```yaml
379
+ version: 1
380
+ displayName: "Monthly payments (due 10th)"
381
+ type: payment
382
+ currency: JPY
383
+ cadence:
384
+ type: monthly
385
+ day: 10
386
+ targets:
387
+ - id: isamu
388
+ displayName: "Isamu"
389
+ defaults:
390
+ amount: 15000
391
+ - id: singularity-society
392
+ displayName: "Singularity Society"
393
+ steps:
394
+ - id: pay
395
+ displayName: "Pay"
396
+ deadline: cycle-deadline
397
+ firingPlan:
398
+ - { at: cycle-start, severity: info }
399
+ - { at: cycle-deadline-3d, severity: warning }
400
+ - { at: cycle-deadline+1d, severity: urgent }
401
+ fields: [invoiceReceivedOn, amount, paidOn]
402
+ formSchema:
403
+ fields:
404
+ - { name: invoiceReceivedOn, type: date, label: "Invoice received on" }
405
+ - { name: amount, type: number, label: "Amount paid (JPY)", required: true }
406
+ - { name: paidOn, type: date, label: "Payment date" }
407
+ ```
408
+
409
+ When the cycle fires, one bell entry covers both targets. The seeded chat lists both; close them with two `markStepDone` calls (one per `targetId`).
410
+
411
+ ### 2. Biannual real estate tax with escalation
412
+
413
+ ```yaml
414
+ version: 1
415
+ displayName: "Real estate tax — Hayama"
416
+ type: payment
417
+ currency: JPY
418
+ cadence:
419
+ type: biannual
420
+ cycles:
421
+ - { month: 4, day: 28 }
422
+ - { month: 11, day: 28 }
423
+ targets:
424
+ - id: hayama-house
425
+ displayName: "Hayama house"
426
+ steps:
427
+ - id: pay
428
+ displayName: "Pay"
429
+ deadline: cycle-deadline
430
+ firingPlan:
431
+ - { at: cycle-deadline-21d, severity: info }
432
+ - { at: cycle-deadline-3d, severity: warning }
433
+ - { at: cycle-deadline+1d, severity: urgent }
434
+ fields: [invoiceReceivedOn, amount, paidOn]
435
+ formSchema:
436
+ fields:
437
+ - { name: invoiceReceivedOn, type: date, label: "Invoice received on" }
438
+ - { name: amount, type: number, label: "Amount paid (JPY)", required: true }
439
+ - { name: paidOn, type: date, label: "Payment date" }
440
+ ```
441
+
442
+ ### 3. Annual physical, multi-step
443
+
444
+ ```yaml
445
+ version: 1
446
+ displayName: "Annual physical"
447
+ type: service
448
+ cadence:
449
+ type: annual
450
+ cycles:
451
+ - { month: 5, day: 28 }
452
+ targets:
453
+ - id: satoshi
454
+ displayName: "Satoshi"
455
+ steps:
456
+ - id: make-appointment
457
+ displayName: "Make appointment"
458
+ deadline: cycle-deadline-30d
459
+ firingPlan:
460
+ - { at: step-deadline-14d, severity: info }
461
+ - { at: step-deadline, severity: warning }
462
+ fields: []
463
+ - id: doctor-visit
464
+ displayName: "Doctor visit"
465
+ deadline: cycle-deadline
466
+ firingPlan:
467
+ - { at: step-deadline-3d, severity: info }
468
+ fields: [visitDate, doctorName, notes]
469
+ formSchema:
470
+ fields:
471
+ - { name: visitDate, type: date, label: "Visit date" }
472
+ - { name: doctorName, type: string, label: "Doctor name" }
473
+ - { name: notes, type: text, label: "Notes" }
474
+ ```
475
+
476
+ Two independent steps with separate deadlines. `step-deadline` inside a step's `firingPlan` refers to that step's own deadline. Closing `make-appointment` leaves `doctor-visit` still open until its own phase fires.
477
+
478
+ ## Operational notes
479
+
480
+ - Encore data lives under `~/mulmoclaude/data/plugins/encore/`. Each obligation is a folder with `index.md` (the DSL + free-form body) and one markdown file per cycle.
481
+ - The bell entry for an obligation is action-lifecycle — clicking it lands the user in a seeded chat, but does NOT clear the bell. You clear it by closing the underlying step via `markStepDone` (or `markTargetSkipped` / `snooze`).
482
+ - The tick is hourly. State-mutating handlers (`defineEncore`, `markStepDone`, `snooze`, …) reconcile after persisting, so newly-due notifications surface within the same SSE turn.
@@ -35,21 +35,23 @@ Under the hood it uses the Claude Code Agent SDK as its LLM core. Claude has ful
35
35
 
36
36
  The wiki (`wiki/` in the workspace) acts as Claude's long-term memory. Unlike the conversation history which resets each session, the wiki is a persistent, compounding knowledge base that Claude builds and maintains over time. You feed it sources — articles, URLs, notes — and Claude ingests them into structured, interlinked Markdown pages. The more you add, the smarter it gets.
37
37
 
38
- See [Wiki](helps/wiki.md) for details on how it works.
38
+ See [Wiki](config/helps/wiki.md) for details on how it works.
39
39
 
40
40
  ## Help Pages
41
41
 
42
- - [Wiki](helps/wiki.md) — how the personal knowledge wiki works, its folder layout, page format, and operations
43
- - [Gemini API Key](helps/gemini.md) — why `GEMINI_API_KEY` is strongly recommended (images, audio, video) and how to get one from Google AI Studio
44
- - [MulmoScript](helps/mulmoscript.md) — format reference for authoring multimedia stories: beats, image types, speech, audio, and a minimal example
45
- - [Business Presentation Template](helps/business.md) — MulmoScript template and rules for business presentations in the Office role
46
- - [Storyteller Template](helps/storyteller.md) — MulmoScript template and rules for character-driven narrated stories in the Storyteller role
47
- - [Guide & Planner Templates](helps/guide.md) — document structures and form-field hints per guide type for the Guide & Planner role
48
- - [Spreadsheet](helps/spreadsheet.md) — cell format, formulas, date handling, and format codes for the presentSpreadsheet plugin
49
- - [presentHtml](helps/presenthtml.md) — self-contained HTML rules and the three-`../` relative-path convention used by the presentHtml plugin to keep generated files portable under `file://`
50
- - [Sandbox](helps/sandbox.md) — how the Docker sandbox isolates the agent, what it can access, and how to disable it
51
- - [Telegram Bridge](helps/telegram.md) — how to talk to MulmoClaude from the Telegram app: creating a bot, starting the bridge, allowlisting chat IDs, commands, and troubleshooting
52
- - [Information Sources](helps/sources.md) — registering RSS feeds / GitHub repos / arXiv queries, the daily-brief pipeline, and where its files live on disk
42
+ - [Wiki](config/helps/wiki.md) — how the personal knowledge wiki works, its folder layout, page format, and operations
43
+ - [Gemini API Key](config/helps/gemini.md) — why `GEMINI_API_KEY` is strongly recommended (images, audio, video) and how to get one from Google AI Studio
44
+ - [MulmoScript](config/helps/mulmoscript.md) — format reference for authoring multimedia stories: beats, image types, speech, audio, and a minimal example
45
+ - [Business Presentation Template](config/helps/business.md) — MulmoScript template and rules for business presentations in the Office role
46
+ - [Storyteller Template](config/helps/storyteller.md) — MulmoScript template and rules for character-driven narrated stories in the Storyteller role
47
+ - [Guide & Planner Templates](config/helps/guide.md) — document structures and form-field hints per guide type for the Guide & Planner role
48
+ - [Spreadsheet](config/helps/spreadsheet.md) — cell format, formulas, date handling, and format codes for the presentSpreadsheet plugin
49
+ - [presentHtml](config/helps/presenthtml.md) — self-contained HTML rules and the three-`../` relative-path convention used by the presentHtml plugin to keep generated files portable under `file://`
50
+ - [Sandbox](config/helps/sandbox.md) — how the Docker sandbox isolates the agent, what it can access, and how to disable it
51
+ - [Telegram Bridge](config/helps/telegram.md) — how to talk to MulmoClaude from the Telegram app: creating a bot, starting the bridge, allowlisting chat IDs, commands, and troubleshooting
52
+ - [Information Sources](config/helps/sources.md) — registering RSS feeds / GitHub repos / arXiv queries, the daily-brief pipeline, and where its files live on disk
53
+ - [Encore — recurring obligations DSL](config/helps/encore-dsl.md) — YAML DSL for recurring obligations (payments, services, check-ins), firing plans, multi-target bundles, and the bell-click resume loop
54
+ - [GitHub repositories in the workspace](config/helps/github.md) — clone-destination rules under `github/<name>/` and how to handle existing directories with matching or different remotes
53
55
 
54
56
  ## Workspace Layout
55
57
 
@@ -60,6 +62,6 @@ See [Wiki](helps/wiki.md) for details on how it works.
60
62
  calendar/ ← calendar events
61
63
  contacts/ ← address book
62
64
  wiki/ ← personal knowledge wiki (long-term memory)
63
- helps/ ← help pages (synced from app on every start)
65
+ config/helps/ ← help pages (synced from app on every start)
64
66
  memory.md ← distilled facts loaded into every session
65
67
  ```
@@ -11,8 +11,8 @@ MulmoScript files are rendered in the canvas. The underlying engine is [mulmocas
11
11
  | Purpose | Provider | Example config |
12
12
  |---|---|---|
13
13
  | TTS (speech) | `gemini` | `"provider": "gemini", "voiceId": "Kore"` |
14
- | Image generation | `google` | `"provider": "google", "model": "gemini-2.5-flash-image"` |
15
- | Video generation | `google` | `"provider": "google", "model": "veo-2.0-generate-001"` |
14
+ | Image generation | `google` | `"provider": "google", "model": "gemini-3.1-flash-image-preview"` |
15
+ | Video generation | `google` | `"provider": "google", "model": "veo-3.1-generate"` |
16
16
 
17
17
  Do not use `openai`, `elevenlabs`, or other providers — they are not configured in this app.
18
18
 
@@ -225,7 +225,7 @@ Control BGM and volume mixing.
225
225
  "Presenter": { "provider": "gemini", "voiceId": "Kore", "displayName": { "en": "Presenter" } }
226
226
  }
227
227
  },
228
- "imageParams": { "provider": "google", "model": "gemini-2.5-flash-image" },
228
+ "imageParams": { "provider": "google", "model": "gemini-3.1-flash-image-preview" },
229
229
  "beats": [
230
230
  {
231
231
  "speaker": "Presenter",
@@ -23,11 +23,11 @@ The container runs with `--cap-drop ALL` and as the host user's UID/GID, so it h
23
23
 
24
24
  ## Disabling the Sandbox
25
25
 
26
- Set the environment variable `DISABLE_SANDBOX=1` to always run the agent directly on the host, even when Docker is available.
26
+ Set the environment variable `DISABLE_SANDBOX=1` to always run the agent directly on the host, even when Docker is available. Equivalently, pass the `--disable-sandbox` CLI flag — `yarn dev --disable-sandbox` or `npx mulmoclaude --disable-sandbox`. The flag form is handy on Windows PowerShell (no inline `VAR=value` syntax), in IDE / launcher run configs, and for quick ad-hoc debugging. Both set the same internal switch; the env var stays supported in parallel.
27
27
 
28
28
  ## Debug aids (opt-in env vars)
29
29
 
30
- These flags exist for development / debugging only. Off by default so production runs aren't surprised.
30
+ These flags exist for development / debugging only. Off by default so production runs aren't surprised. Each has an equivalent `--flag` CLI form (drop the `=1`, kebab-case the name) accepted by both `yarn dev` and `npx mulmoclaude` — e.g. `DISABLE_SANDBOX=1` ≡ `--disable-sandbox`, `PERSIST_TOOL_CALLS=1` ≡ `--persist-tool-calls`, `DISABLE_MACOS_REMINDER_NOTIFICATIONS=1` ≡ `--disable-macos-reminders`, `JOURNAL_FORCE_RUN_ON_STARTUP=1` ≡ `--journal-force-run`, `CHAT_INDEX_FORCE_RUN_ON_STARTUP=1` ≡ `--chat-index-force-run`. Secret-bearing vars (auth token, API keys) have no flag form on purpose — argv is visible via `ps` / shell history.
31
31
 
32
32
  - `DISABLE_SANDBOX=1` — see above. Bypasses the Docker sandbox.
33
33
  - `PERSIST_TOOL_CALLS=1` — also persist `tool_call` events to the session jsonl alongside `tool_result`. Useful for reading the args sent to a tool after the run is over (page refresh / server restart). Off by default because args can be large and may carry payload bytes (inline images, full MulmoScript JSON) you didn't expect to land in the jsonl. See [issue #1096](https://github.com/receptron/mulmoclaude/issues/1096) for the rationale.
@@ -6,11 +6,13 @@
6
6
  // settings.json never changes.
7
7
  //
8
8
  // THIS FILE IS THE SOURCE OF TRUTH. Edits here, then run
9
- // `yarn build:hooks` (or `yarn build`) to regenerate `./dispatcher.mjs`.
10
- // The bundled `.mjs` is committed to git so `provision.ts` can read
11
- // it at server startup without invoking esbuild on the runtime path.
12
- // CI runs `yarn build:hooks && git diff --exit-code` to catch a
13
- // stale bundle.
9
+ // `yarn build:hooks` (or `yarn build`) to regenerate the bundle at
10
+ // `server/build/dispatcher.mjs` (generated output is kept out of the
11
+ // source tree). The bundled `.mjs` is committed to git so
12
+ // `provision.ts` can read it at server startup without invoking
13
+ // esbuild on the runtime path. CI's "Verify built hook bundle"
14
+ // step runs `git diff --exit-code` on the committed bundle to catch
15
+ // a stale commit.
14
16
  //
15
17
  // The hook executes inside Claude CLI's process space — must be a
16
18
  // self-contained ESM bundle. esbuild rolls every import (handlers,
@@ -33,12 +33,15 @@ import { errorMessage } from "../../utils/errors.js";
33
33
  // The esbuild bundle is the source of truth for the dispatcher
34
34
  // script written into <workspace>/.claude/hooks/. Source TS is
35
35
  // `dispatcher.ts`; `yarn build:hooks` (chained from `yarn build`)
36
- // regenerates `dispatcher.mjs`, and CI fails when the committed
37
- // bundle drifts from the source. The read happens inside
36
+ // regenerates the bundle at `server/build/dispatcher.mjs` (generated
37
+ // output is kept out of the source tree). The read happens inside
38
38
  // `provisionDispatcherHook` (not at module load) so a missing /
39
39
  // unreadable bundle degrades to a logged warning without breaking
40
40
  // server startup.
41
- const DISPATCHER_BUNDLE_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "dispatcher.mjs");
41
+ //
42
+ // This module lives at server/workspace/hooks/provision.ts, so
43
+ // `../../build/dispatcher.mjs` resolves to server/build/dispatcher.mjs.
44
+ const DISPATCHER_BUNDLE_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "build", "dispatcher.mjs");
42
45
 
43
46
  function readDispatcherBundle(): string | null {
44
47
  try {
@@ -12,10 +12,9 @@
12
12
  // artifacts/ LLM-generated (charts, html, images, documents,
13
13
  // spreadsheets, stories, news)
14
14
  //
15
- // Existing workspaces need the one-shot `scripts/migrate-workspace-284.ts`
16
- // script run before first startup with this code. `server/workspace.ts`
17
- // detects the pre-migration layout at boot and aborts with a pointer
18
- // to the script.
15
+ // Pre-#284 workspaces (with `chat/`, `summaries/`, `memory.md` at the
16
+ // root) continue to boot the modern layout above is what new
17
+ // installs use, but the older directory names are still accepted.
19
18
  //
20
19
  // When adding a new top-level directory: add the name to the
21
20
  // `WORKSPACE_DIRS` record below. The absolute path is derived
@@ -154,6 +153,16 @@ const HOST_WORKSPACE_DIRS = {
154
153
  // so server code references it through `WORKSPACE_PATHS.claudeSkills`
155
154
  // instead of inlining the string.
156
155
  claudeSkills: ".claude/skills",
156
+ // Skill catalog root (#1335). Holds preset skills shipped with the
157
+ // launcher (`catalog/preset/`) and — in later PRs — git-synced
158
+ // Anthropic skills (`catalog/anthropic/`) and community URL-install
159
+ // entries (`catalog/community/`). Entries here are catalog-only:
160
+ // visible to UI / tooling but NOT discovered by Claude Code's
161
+ // slash-command resolver. An entry becomes active by being copied
162
+ // (or symlinked) into `.claude/skills/`. The catalog vs active
163
+ // split keeps unused skills out of the system prompt.
164
+ skillsCatalog: "data/skills/catalog",
165
+ skillsCatalogPreset: "data/skills/catalog/preset",
157
166
  // Nested subdirs inside a top-level grouping. Kept here (rather
158
167
  // than module-local constants) when multiple modules need to
159
168
  // reference the same nested path — e.g. wiki/pages/ is used by