@mulmoclaude/core 1.4.0 → 1.6.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.
@@ -0,0 +1,130 @@
1
+ # Bug-report FAQ — check these before calling it a bug
2
+
3
+ This file is an **index, not an answer sheet**. It never states a value ("the default is off"), only
4
+ where today's truth lives, because the agent reads the real thing at run time. Values rot silently;
5
+ a config key or a file path cannot — rename one and the implementation stops working, so it gets
6
+ fixed.
7
+
8
+ Each entry's `configKey:` / `source:` / `help:` lines are **verified by CI**: a key that no longer
9
+ exists, or a path that moved, fails the test rather than misleading a user months later.
10
+
11
+ Read in this order, because only the first two are available to every user:
12
+
13
+ 1. **`configKey:`** — the live value, from `config/settings.json` in the workspace. Absent from the
14
+ file means the setting is at its default, which is the answer to most entries here.
15
+ 2. **`help:`** — another page in `config/helps/`, always present.
16
+ 3. **`source:`** — a path in the MulmoClaude **repository**. Only readable when running from a clone
17
+ (`yarn dev`); an `npx mulmoclaude` user has no repo, so never tell them to open one. It is listed
18
+ so CI can prove the pointer is still real, and for whoever ends up fixing the code.
19
+
20
+ Entry format:
21
+
22
+ ```
23
+ ## The symptom, in the words a user would say
24
+
25
+ configKey: <a key in config/settings.json> (optional, repeatable)
26
+ source: <path in the MulmoClaude repo> (optional, repeatable)
27
+ help: <another page in config/helps/> (optional, repeatable)
28
+
29
+ What to check. Never what the value is.
30
+ ```
31
+
32
+ Maintained by hand, in the repo. This file is the source of truth; the issue tracker is the inbox.
33
+
34
+ ---
35
+
36
+ ## Voice input does nothing, or the mic button is disabled
37
+
38
+ configKey: voiceInput
39
+ source: packages/core/src/whisper
40
+ help: error-recovery.md
41
+
42
+ Read `voiceInput` from the live settings — a key absent from the file has never been touched, and
43
+ turning it on is what triggers the model download. Three separate conditions gate the mic button:
44
+ platform capability, the opt-in, and whether the model finished downloading. `GET /api/health`
45
+ reports all three as `voiceInput`, so check it before deciding which one is missing — "on but still
46
+ greyed out" is a different question from "never turned on".
47
+
48
+ ## I don't get a notification when a task finishes
49
+
50
+ configKey: pushEnabled
51
+ source: server/agent/webPush.ts
52
+ help: remote-host.md
53
+
54
+ Two conditions must hold at once, so read both before calling it a bug. `pushEnabled` in the live
55
+ settings is the user's opt-in. The RemoteHost channel must also be connected — that connection is
56
+ what supplies the Firebase auth, so with the phone link down the send is a no-op by design. A user
57
+ who never connected a phone has nothing to receive the push regardless of the setting.
58
+
59
+ ## My chats never get titles or summaries
60
+
61
+ configKey: chatIndex
62
+ source: server/workspace/chat-index/indexer.ts
63
+
64
+ Read `chatIndex` — it names the model the background summarizer spawns, and the summarizer does
65
+ nothing until it does. One exception worth knowing before calling it a bug: sessions that didn't
66
+ originate from the user (`system`, `scheduler`) are always skipped whatever the setting says, so a
67
+ scheduled task's chat having no title is expected.
68
+
69
+ ## The journal / daily summary is empty
70
+
71
+ configKey: journal
72
+ source: server/workspace/journal/index.ts
73
+
74
+ Read `journal` first — it gates the archivist that summarizes chat sessions into `journal/*.md`,
75
+ and when it is not enabling a run the archivist short-circuits before the interval gate is even
76
+ consulted. Neither the turn-end hook nor the hourly scheduled task will have written anything, so
77
+ an empty journal is not evidence of a scheduler fault.
78
+
79
+ ## Gmail / Google Calendar / Notion tools don't show up for the agent
80
+
81
+ configKey: extraAllowedTools
82
+ source: server/agent/config.ts
83
+
84
+ Connector tools reach the agent only when their MCP prefix is listed in `extraAllowedTools` — the
85
+ list is appended to the base allowed-tools set on every spawn. Check the live value for the
86
+ expected `mcp__…` prefix, and remember the connector must also be linked on the Claude side. A tool
87
+ that is linked but unlisted is configuration, not a bug.
88
+
89
+ ## A skill I saved isn't callable
90
+
91
+ source: server/workspace/hooks/handlers/skillBridge.ts
92
+ source: server/workspace/skills/discovery.ts
93
+ help: collection-skills.md
94
+
95
+ Skills are authored at `data/skills/<slug>/SKILL.md` and mirrored into `.claude/skills/<slug>/` by a
96
+ hook — agents must not write the latter directly. If the staging file exists but the mirror doesn't,
97
+ the hook is the thing to look at. Bundled `mc-*` presets are a different case: they land in the
98
+ catalog (`data/skills/catalog/preset/`) and are **not** active until the user stars one, so "the
99
+ preset skill doesn't respond" is usually "it was never activated".
100
+
101
+ ## I edited an mc-* skill and my changes came back reverted
102
+
103
+ source: packages/core/src/workspace-setup/sync.ts
104
+
105
+ Working as designed: `mc-*` presets are launcher-managed factory defaults, refreshed from the
106
+ shipped copy on every server boot so bug fixes and renames actually reach an already-starred copy.
107
+ Any file it overwrites is first saved next to it as `<file>.bak.<timestamp>`, so the user's edits
108
+ are recoverable — point them at that instead of re-typing. To keep changes permanently, copy the
109
+ skill to a new non-`mc-` slug.
110
+
111
+ ## The agent can't use git / gh / ssh
112
+
113
+ help: error-recovery.md
114
+ help: sandbox.md
115
+
116
+ The agent's Docker sandbox exposes no host SSH agent and no `gh` config unless the user opted them
117
+ in at start-up. `GET /api/sandbox` reports whether the sandbox is active and which config mounts it
118
+ was started with — read that rather than assuming either way. This is the single most common "it's
119
+ broken" report and it is configuration. `error-recovery.md` has the exact flags and the
120
+ verification commands; don't restate them from memory.
121
+
122
+ ## A collection's custom view is blank
123
+
124
+ help: custom-view.md
125
+ help: custom-view-remote.md
126
+
127
+ Desktop and phone views have **incompatible runtime contracts** — a desktop view fetches its own
128
+ records with an injected token, while a `target: "mobile"` view gets them over a postMessage bridge
129
+ and cannot `fetch` at all. A view authored against the wrong contract renders empty with no error.
130
+ Check which target it is registered as before treating it as a rendering fault.
@@ -1,10 +1,15 @@
1
- # Error recovery — when a tool call fails
1
+ # Error recovery — when a tool call fails, or the user says something is broken
2
2
 
3
3
  This is the lookup the agent reads BEFORE asking the user a clarifying
4
4
  question or giving up on a failing tool call. Each section is keyed by
5
5
  the error message you'd see in tool output, with the cause and the
6
6
  documented fix.
7
7
 
8
+ It has a second entry point. When the **user** reports that MulmoClaude is
9
+ broken / weird / not working — nothing has failed in tool output, they are
10
+ just describing a symptom — start at **§ The user says MulmoClaude is
11
+ broken** below instead of scanning the error-keyed sections.
12
+
8
13
  Cite the section you used in your reply so the user can follow up
9
14
  (e.g. "Per `config/helps/error-recovery.md` § gh-auth / SSH …").
10
15
 
@@ -13,6 +18,134 @@ If no section here matches, list the workspace's other help files
13
18
  area (`sandbox.md`, `github.md`, `collection-skills.md`, etc.) before
14
19
  falling back to asking the user.
15
20
 
21
+ ## The user says MulmoClaude is broken
22
+
23
+ **The goal is that the user ends up unblocked, not that an issue gets filed.**
24
+ An issue is what's left after the first three steps fail to explain the
25
+ behaviour. Work them in order and stop the moment the user is unblocked.
26
+
27
+ Rules that hold in every step:
28
+
29
+ - **Never assert "that's by design" from memory.** Say it only with a reason
30
+ attached: a config key, a help page, an implementation file.
31
+ `bug-report-faq.md` is an index of where to look — it deliberately contains
32
+ no values.
33
+ - **Read the real thing.** Current values come from the workspace's
34
+ `config/settings.json` or `GET /api/health`, never from recollection.
35
+ - **"I don't know" is an allowed answer.** If a step can't decide, say so and
36
+ go to the next one. Never close the conversation by guessing.
37
+ - **Nothing leaves the machine before the user has seen it in full** and agreed.
38
+ - Reply in the language the user writes in.
39
+
40
+ ### Step 1 — Hear the symptom (collect nothing yet)
41
+
42
+ Call `presentForm` once with the questions below rather than asking in prose —
43
+ a user who is already frustrated should be clicking, not composing.
44
+
45
+ - **What kind** — display looks wrong / input doesn't work / the agent won't
46
+ answer / a tool keeps failing / phone or remote features / something else
47
+ - **Where** — chat / a collection / the wiki / files / settings / the phone app
48
+ - **How often** — every time / sometimes / once
49
+ - **Since when** — after an update / it always did this / not sure
50
+ - **What did you expect, and what happened instead?** (free text, required —
51
+ these two sentences are what the next step is judged against)
52
+
53
+ ### Step 2 — Is it configuration, or by design? (this is the actual job)
54
+
55
+ 1. Read `config/helps/bug-report-faq.md` and find the entry closest to the
56
+ symptom.
57
+ 2. Follow its pointers to the **real values** — `config/settings.json` for a
58
+ `configKey`, the named help page for a `help`. A setting absent from the file
59
+ is at its default, which is the answer to most entries there.
60
+ 3. If that explains the gap between expected and actual, **say why, show the
61
+ fix, and stop.** Cite what you checked.
62
+ 4. If nothing explains it, go to Step 3. Do not stretch a FAQ entry to cover a
63
+ symptom it doesn't cover.
64
+
65
+ ### Step 3 — Is it already known?
66
+
67
+ Search the existing issues before opening anything:
68
+
69
+ ```bash
70
+ gh issue list --repo receptron/mulmoclaude --state all --limit 20 --search "<symptom keywords>"
71
+ ```
72
+
73
+ `gh` is **not available by default** — the agent runs in a credential-free
74
+ sandbox (see § gh / git / SSH errors inside the sandbox). Don't spend turns
75
+ fighting it: hand the user the search URL instead.
76
+
77
+ `https://github.com/receptron/mulmoclaude/issues?q=voice+input+disabled` — build
78
+ the `q=` value percent-encoded (space → `+` or `%20`, `#` → `%23`), or the link
79
+ breaks on the first symptom that contains one.
80
+
81
+ - **Closed and fixed** → compare their version against the release that fixed
82
+ it. If they're simply behind, tell them to update and stop.
83
+ - **Open** → do not open a second one. Offer to add this user's environment and
84
+ repro steps as a comment; a second reproduction is worth more than a duplicate.
85
+ - **Nothing found** → Step 4.
86
+
87
+ ### Step 4 — File it
88
+
89
+ Only now collect details. Fetch the environment report from the server, from the
90
+ workspace root:
91
+
92
+ ```bash
93
+ curl -s -H "Authorization: Bearer $(cat .session-token)" \
94
+ "http://${MULMOCLAUDE_HOST:-127.0.0.1}:$(cat .server-port)/api/diagnostics/report"
95
+ ```
96
+
97
+ Three parts of that command are load-bearing, and dropping any one returns
98
+ nothing useful:
99
+
100
+ - **The bearer header.** Every `/api/*` route requires it; without it the reply
101
+ is a 401, not a report. The token is regenerated each startup and lives in
102
+ `.session-token` at the workspace root.
103
+ - **`$MULMOCLAUDE_HOST`.** In the default Docker sandbox `localhost` is the
104
+ container, not the machine running the server — the variable is set to
105
+ `host.docker.internal` there and unset on a host-mode run, hence the fallback.
106
+ - **`.server-port`.** The port is chosen at startup; there is no fixed default
107
+ to hardcode.
108
+
109
+ Keep the token inside the command substitution: never echo it, never let it
110
+ reach the report body, and if you quote the command in the issue, quote it in
111
+ the `$(cat …)` form above rather than with the value expanded.
112
+
113
+ **The server does the redaction, not you.** It prints values only for
114
+ allow-listed settings and withholds everything else, including the plaintext
115
+ Google Maps key and every MCP server's `env` / `headers`. Paste what it returns
116
+ verbatim — do not "help" by adding values you read elsewhere, and do not
117
+ re-type a secret you happened to see in a file.
118
+
119
+ Ask the user for what the host cannot see: the browser and its version, any red
120
+ errors in the browser console, and a screenshot if the symptom is visual. Say
121
+ before they attach one that a screenshot can carry file paths and chat text.
122
+
123
+ Report body:
124
+
125
+ ```markdown
126
+ ## What happened
127
+ ## What I expected
128
+ ## Steps to reproduce
129
+ 1.
130
+ ## Environment
131
+ (paste the diagnostics report here)
132
+ ## Attachments
133
+ ```
134
+
135
+ Show the whole thing, get an explicit yes, then post it — `gh issue create
136
+ --repo receptron/mulmoclaude --title "<title>" --body-file <file>` when `gh`
137
+ works, otherwise print the markdown for copy-paste plus
138
+ `https://github.com/receptron/mulmoclaude/issues/new`.
139
+
140
+ ### When Step 2 resolved it
141
+
142
+ A question that took an agent to answer is a signal about the product: the UI
143
+ failed to say something. Offer to post it as an issue titled with the question
144
+ as the user asked it, noting what the answer turned out to be and **where it was
145
+ checked**. Open the body with a line saying the answer came from this lookup and
146
+ is awaiting maintainer review — it is a draft, not documentation. Never edit
147
+ `bug-report-faq.md` yourself.
148
+
16
149
  ## gh / git / SSH errors inside the sandbox
17
150
 
18
151
  ### Symptoms
@@ -67,7 +67,8 @@ See [Wiki](config/helps/wiki.md) for details on how it works.
67
67
  - [Spreadsheet](config/helps/spreadsheet.md) — cell format, formulas, date handling, and format codes for the presentSpreadsheet plugin
68
68
  - [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://`
69
69
  - [Sandbox](config/helps/sandbox.md) — how the Docker sandbox isolates the agent, what it can access, and how to disable it
70
- - [Error recovery](config/helps/error-recovery.md) — the lookup the agent reads on tool failures (gh/git/SSH inside the sandbox, Marp PDF, registry import, build/workspace, plugin runtime) before asking the user
70
+ - [Error recovery](config/helps/error-recovery.md) — the lookup the agent reads on tool failures (gh/git/SSH in the sandbox, Marp PDF, registry import, build/workspace, plugin runtime), plus the four-step triage for when a user reports something broken
71
+ - [Bug-report FAQ](config/helps/bug-report-faq.md) — symptoms that turn out to be configuration or by design (voice input, push, chat titles, journal, connector tools, preset skills, custom views); says where to read the live value, never what it is
71
72
  - [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
72
73
  - [Remote host](config/helps/remote-host.md) — drive MulmoClaude from a phone at mulmoserver.web.app: Google sign-in connect, host online vs. offline (queued chats, 7-day expiry), photo attachments, and the security model
73
74
  - [Feeds](config/helps/feeds.md) — register a self-refreshing data feed (RSS/Atom/JSON) by authoring `feeds/<slug>/schema.json`: schema shape, the `ingest` block, raw-item field mapping, and `maxItems` retention
@@ -1179,6 +1179,7 @@ var TASKS_BASE_URL = "https://tasks.googleapis.com/tasks/v1";
1179
1179
  var TASKS_API_LABEL = "Google Tasks API";
1180
1180
  var DEFAULT_TASK_LIST_ID = "@default";
1181
1181
  var TASK_STATUS_COMPLETED = "completed";
1182
+ var TASK_STATUS_NEEDS_ACTION = "needsAction";
1182
1183
  var MAX_TASK_LISTS = 50;
1183
1184
  var toTaskListSummary = (value) => {
1184
1185
  const record = asRecord(value);
@@ -1226,8 +1227,9 @@ async function createTask(accessToken, input) {
1226
1227
  }
1227
1228
  /** PATCH body for a task edit — only the fields the caller supplied. As with
1228
1229
  * events, `undefined` means "leave as is" and `""` means "clear it", so the
1229
- * two must stay distinct. `status` is deliberately absent: `completeTask`
1230
- * owns that transition, and two ways to set it would drift apart. */
1230
+ * two must stay distinct. `status` is deliberately absent: `completeTask` /
1231
+ * `uncompleteTask` own that transition, and two ways to set it would drift
1232
+ * apart. */
1231
1233
  var buildTaskPatch = (input) => ({
1232
1234
  ...input.title !== void 0 ? { title: input.title } : {},
1233
1235
  ...input.notes !== void 0 ? { notes: input.notes } : {},
@@ -1245,6 +1247,25 @@ async function completeTask(accessToken, input) {
1245
1247
  body: JSON.stringify({ status: TASK_STATUS_COMPLETED })
1246
1248
  }));
1247
1249
  }
1250
+ /** Send a completed task back to the to-do list.
1251
+ *
1252
+ * Its own function rather than a flag on `completeTask`, and deliberately not
1253
+ * a `status` field on `updateTask`: one kind per target state keeps the name
1254
+ * honest and leaves exactly one code path setting each value.
1255
+ *
1256
+ * The patch carries `status` alone — mirroring `completeTask`, which also
1257
+ * sets only `status` and lets Google fill in the `completed` timestamp. Note
1258
+ * that whether Google *clears* that timestamp on the way back is its
1259
+ * behaviour, not ours, and is unverified here: `TaskSummary` doesn't carry
1260
+ * `completed`, so nothing in this codebase would show a stale one. If a
1261
+ * reopened task ever displays a completion date in Google's own UI, this is
1262
+ * the place to add `completed: null` to the patch. */
1263
+ async function uncompleteTask(accessToken, input) {
1264
+ return toTaskSummary(await googleRequest(TASKS_API_LABEL, accessToken, tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`), {
1265
+ method: "PATCH",
1266
+ body: JSON.stringify({ status: TASK_STATUS_NEEDS_ACTION })
1267
+ }));
1268
+ }
1248
1269
  async function deleteTask(accessToken, input) {
1249
1270
  await googleRequest(TASKS_API_LABEL, accessToken, tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`), { method: "DELETE" });
1250
1271
  }
@@ -1423,6 +1444,7 @@ exports.toDriveFileSummary = toDriveFileSummary;
1423
1444
  exports.toEventSummary = toEventSummary;
1424
1445
  exports.toTaskListSummary = toTaskListSummary;
1425
1446
  exports.toTaskSummary = toTaskSummary;
1447
+ exports.uncompleteTask = uncompleteTask;
1426
1448
  exports.unlinkGoogle = unlinkGoogle;
1427
1449
  exports.unsyncedGroups = unsyncedGroups;
1428
1450
  exports.updateCalendarEvent = updateCalendarEvent;