@mulmoclaude/core 4.8.0 → 4.9.1

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 (93) hide show
  1. package/assets/helps/bug-report-faq.md +12 -0
  2. package/assets/helps/custom-view.md +1 -1
  3. package/assets/helps/error-recovery.md +234 -26
  4. package/assets/helps/index.md +1 -1
  5. package/assets/helps/sandbox.md +2 -2
  6. package/assets/helps/telegram.md +10 -4
  7. package/dist/collection/index.cjs +3 -2
  8. package/dist/collection/index.cjs.map +1 -1
  9. package/dist/collection/index.js +3 -2
  10. package/dist/collection/index.js.map +1 -1
  11. package/dist/collection/registry/server/index.cjs +3 -3
  12. package/dist/collection/registry/server/index.js +3 -3
  13. package/dist/collection/server/index.cjs +2 -2
  14. package/dist/collection/server/index.js +2 -2
  15. package/dist/collection-watchers/index.cjs +4 -4
  16. package/dist/collection-watchers/index.js +4 -4
  17. package/dist/{discovery-BY-nMCuh.js → discovery-BkIKvdh0.js} +3 -3
  18. package/dist/{discovery-BY-nMCuh.js.map → discovery-BkIKvdh0.js.map} +1 -1
  19. package/dist/{discovery-2TvulIz8.cjs → discovery-tvFo-bUJ.cjs} +3 -3
  20. package/dist/{discovery-2TvulIz8.cjs.map → discovery-tvFo-bUJ.cjs.map} +1 -1
  21. package/dist/{dist-D8zokgGo.js → dist-BzoA9pDR.js} +6 -1
  22. package/dist/dist-BzoA9pDR.js.map +1 -0
  23. package/dist/{dist-CsgSfWwR.cjs → dist-Gj7ygaW2.cjs} +6 -1
  24. package/dist/dist-Gj7ygaW2.cjs.map +1 -0
  25. package/dist/errorMessage-BQNpSYdT.js +1 -0
  26. package/dist/errorMessage-CT-va_Pv.cjs +1 -0
  27. package/dist/errors-C7TI_t_4.js +10 -0
  28. package/dist/errors-C7TI_t_4.js.map +1 -0
  29. package/dist/errors-CdXsfFCF.cjs +15 -0
  30. package/dist/errors-CdXsfFCF.cjs.map +1 -0
  31. package/dist/feeds/server/index.cjs +4 -4
  32. package/dist/feeds/server/index.js +4 -4
  33. package/dist/google/index.cjs +3 -2
  34. package/dist/google/index.cjs.map +1 -1
  35. package/dist/google/index.js +3 -2
  36. package/dist/google/index.js.map +1 -1
  37. package/dist/{listen-UZA68Upt.cjs → listen-BXthb-9C.cjs} +2 -2
  38. package/dist/{listen-UZA68Upt.cjs.map → listen-BXthb-9C.cjs.map} +1 -1
  39. package/dist/{listen-D5av7GzB.js → listen-Dca-NoBi.js} +2 -2
  40. package/dist/{listen-D5av7GzB.js.map → listen-Dca-NoBi.js.map} +1 -1
  41. package/dist/notifier/index.cjs +1 -1
  42. package/dist/notifier/index.js +1 -1
  43. package/dist/notifier/types.d.ts +20 -11
  44. package/dist/{notifier-DZJj0sOh.cjs → notifier-B65Mvngb.cjs} +12 -7
  45. package/dist/notifier-B65Mvngb.cjs.map +1 -0
  46. package/dist/{notifier-DhnwR82U.js → notifier-vAp5t6wL.js} +12 -7
  47. package/dist/notifier-vAp5t6wL.js.map +1 -0
  48. package/dist/plugin-vue/index.cjs +31 -12
  49. package/dist/plugin-vue/index.cjs.map +1 -1
  50. package/dist/plugin-vue/index.js +31 -12
  51. package/dist/plugin-vue/index.js.map +1 -1
  52. package/dist/{promptSafety-Bte62OvU.js → promptSafety-BpBTh5xO.js} +2 -2
  53. package/dist/{promptSafety-Bte62OvU.js.map → promptSafety-BpBTh5xO.js.map} +1 -1
  54. package/dist/{promptSafety-Cv-SoTd4.cjs → promptSafety-caJWd1ZQ.cjs} +2 -2
  55. package/dist/{promptSafety-Cv-SoTd4.cjs.map → promptSafety-caJWd1ZQ.cjs.map} +1 -1
  56. package/dist/remote-host/index.cjs +1 -1
  57. package/dist/remote-host/index.js +1 -1
  58. package/dist/remote-host/server/index.cjs +30 -7
  59. package/dist/remote-host/server/index.cjs.map +1 -1
  60. package/dist/remote-host/server/index.js +30 -7
  61. package/dist/remote-host/server/index.js.map +1 -1
  62. package/dist/remote-host/server/sessionPersistence.d.ts +7 -2
  63. package/dist/{remote-host-CFnl1I-L.js → remote-host-Cjox2F2J.js} +2 -2
  64. package/dist/{remote-host-CFnl1I-L.js.map → remote-host-Cjox2F2J.js.map} +1 -1
  65. package/dist/{remote-host-EQPlccB9.cjs → remote-host-DhAHBYzN.cjs} +2 -2
  66. package/dist/{remote-host-EQPlccB9.cjs.map → remote-host-DhAHBYzN.cjs.map} +1 -1
  67. package/dist/remote-view/index.cjs +1 -1
  68. package/dist/remote-view/index.js +1 -1
  69. package/dist/scheduler/index.cjs +2 -1
  70. package/dist/scheduler/index.cjs.map +1 -1
  71. package/dist/scheduler/index.js +2 -1
  72. package/dist/scheduler/index.js.map +1 -1
  73. package/dist/{server-DXUt34Rt.cjs → server-Div1i_k5.cjs} +5 -4
  74. package/dist/{server-DXUt34Rt.cjs.map → server-Div1i_k5.cjs.map} +1 -1
  75. package/dist/{server-DyYwfNB6.js → server-Dk9x1VLG.js} +5 -4
  76. package/dist/{server-DyYwfNB6.js.map → server-Dk9x1VLG.js.map} +1 -1
  77. package/dist/utils/index.cjs +3 -10
  78. package/dist/utils/index.js +2 -9
  79. package/dist/whisper/index.cjs +2 -1
  80. package/dist/whisper/index.cjs.map +1 -1
  81. package/dist/whisper/index.js +2 -1
  82. package/dist/whisper/index.js.map +1 -1
  83. package/dist/wiki/index.cjs +1 -1
  84. package/dist/wiki/index.js +1 -1
  85. package/dist/workspace-setup/index.js +5 -0
  86. package/dist/workspace-setup/index.js.map +1 -1
  87. package/package.json +7 -7
  88. package/dist/dist-CsgSfWwR.cjs.map +0 -1
  89. package/dist/dist-D8zokgGo.js.map +0 -1
  90. package/dist/notifier-DZJj0sOh.cjs.map +0 -1
  91. package/dist/notifier-DhnwR82U.js.map +0 -1
  92. package/dist/utils/index.cjs.map +0 -1
  93. package/dist/utils/index.js.map +0 -1
@@ -56,6 +56,18 @@ settings is the user's opt-in. The RemoteHost channel must also be connected —
56
56
  what supplies the Firebase auth, so with the phone link down the send is a no-op by design. A user
57
57
  who never connected a phone has nothing to receive the push regardless of the setting.
58
58
 
59
+ ## MulmoClaude is answering with a different model than I expected
60
+
61
+ configKey: chatModel
62
+ source: server/agent/config.ts
63
+
64
+ Read `chatModel` from the live settings. Absent means MulmoClaude passes no `--model` at all and the
65
+ CLI resolves the model from `~/.claude/settings.json` — the same file the VS Code and Cursor Claude
66
+ Code extensions write their `/model` pick to, so a switch made in another editor changes MulmoClaude
67
+ too, with nothing on screen to say so. Setting the key from Settings → Model is what pins MulmoClaude
68
+ independently of that shared file. A session already running keeps its model until the next turn, so
69
+ "it changed partway through a conversation" is the expected shape of this, not a second bug.
70
+
59
71
  ## My chats never get titles or summaries
60
72
 
61
73
  configKey: chatIndex
@@ -64,7 +64,7 @@ would run ahead of it and see no `__MC_VIEW`; there is no reason to write one.)
64
64
  window.__MC_VIEW = {
65
65
  slug: "annual-plan", // this collection
66
66
  token: "<scoped capability token>", // Authorization bearer
67
- dataUrl: "http://localhost:3001/api/collections/annual-plan/view-data",
67
+ dataUrl: "http://127.0.0.1:<port>/api/collections/annual-plan/view-data", // absolute, filled in by the host
68
68
  onChange: (cb) => unsubscribe, // live refresh — see "Staying live" below
69
69
  searchQuery: "", // live text in the app's own search box — see "One search box"
70
70
  onSearchQueryChange: (cb) => unsubscribe, // fires when the user types there
@@ -124,11 +124,17 @@ Report body:
124
124
 
125
125
  ```markdown
126
126
  ## What happened
127
+
127
128
  ## What I expected
129
+
128
130
  ## Steps to reproduce
131
+
129
132
  1.
133
+
130
134
  ## Environment
135
+
131
136
  (paste the diagnostics report here)
137
+
132
138
  ## Attachments
133
139
  ```
134
140
 
@@ -280,7 +286,7 @@ naming the file:
280
286
  quotes, unquoted key.
281
287
  - `role file does not match the role schema, skipping` — the `issues`
282
288
  field names each field, e.g. `icon: Invalid input: expected string,
283
- received undefined`. All of `id`, `name`, `icon`, `prompt`,
289
+ received undefined`. All of `id`, `name`, `icon`, `prompt`,
284
290
  `availablePlugins` are required; `availablePlugins` must be an array
285
291
  even for one entry.
286
292
  - `role file is empty, skipping` — zero-length or whitespace only.
@@ -328,7 +334,7 @@ it:
328
334
  `my role.json`, rejected as `Invalid role id 'my role'.` Neither name
329
335
  reaches the role. Renaming is the only fix.
330
336
  - `… the id is not a usable role id` — the reverse, e.g. `"id": "my
331
- role"` in `designer.json`. `delete designer` still works, and renaming
337
+ role"` in `designer.json`. `delete designer` still works, and renaming
332
338
  to `my role.json` would take that away. Change the `id`.
333
339
  - `… neither is a usable role id` — pick one id that matches the pattern
334
340
  and use it for the file name and the `id` together.
@@ -926,12 +932,12 @@ They are genuinely different failures — a permanent load failure, a startup ra
926
932
  and guessing between them is what makes this expensive. Since #2842 the log answers it directly,
927
933
  so read these three before forming a theory:
928
934
 
929
- | Log line | What it tells you |
930
- |---|---|
931
- | `spawning agent … broker=tsx` | This install is on the SLOW path: the bundle is missing, so the broker is transcoded from source on every spawn (seconds to tens of seconds over a Windows/macOS bind mount). A `broker bundle missing` warn accompanies it once per process. |
932
- | `[mcp] broker ready bootMs=… initializeMs=…` | The broker DID connect, and how long it took. `broker cold boot is slow` replaces it past 5 s. |
933
- | `brokerEverReady=false reason=never-ready` on the retry warn | No beacon arrived for that chat, and the host kept looking until the beacon's own delivery budget was spent — the broker did not come up at all. The turn is NOT replayed: a replay would sit out another full connect wait and end in the same error. |
934
- | `reason=ready-during-wait` on the retry warn | The beacon arrived while the host waited, so the broker lost the race by a moment and IS connected now. The turn is replayed, which is what fixes this one. |
935
+ | Log line | What it tells you |
936
+ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
937
+ | `spawning agent … broker=tsx` | This install is on the SLOW path: the bundle is missing, so the broker is transcoded from source on every spawn (seconds to tens of seconds over a Windows/macOS bind mount). A `broker bundle missing` warn accompanies it once per process. |
938
+ | `[mcp] broker ready bootMs=… initializeMs=…` | The broker DID connect, and how long it took. `broker cold boot is slow` replaces it past 5 s. |
939
+ | `brokerEverReady=false reason=never-ready` on the retry warn | No beacon arrived for that chat, and the host kept looking until the beacon's own delivery budget was spent — the broker did not come up at all. The turn is NOT replayed: a replay would sit out another full connect wait and end in the same error. |
940
+ | `reason=ready-during-wait` on the retry warn | The beacon arrived while the host waited, so the broker lost the race by a moment and IS connected now. The turn is replayed, which is what fixes this one. |
935
941
  | `brokerEverStarted=` on the `MCP tools were unavailable` warn | Whether the broker PROCESS ever existed, which is a different question from whether it answered. `true` with `brokerEverReady=false` means it launched and never finished booting — the boot is the problem (the mount, the `tsx` path). `false` means it never launched — the spawn is the problem. Diagnostic only, and not authenticated: under Docker everything needed to forge it sits in the per-session MCP config inside the workspace mount, so read it as evidence about a healthy install rather than as proof against a hostile one. |
936
942
 
937
943
  ### Fix
@@ -968,7 +974,7 @@ so read these three before forming a theory:
968
974
 
969
975
  - The `google` tool (or a `google.calendar.*` remote command) fails with **"Google account not linked on this host"**.
970
976
  - **"Google sign-in service unreachable"** or **"Google sign-in service returned HTTP …"**.
971
- - **"multiple client_secret_*.json files found"**.
977
+ - **"multiple client_secret\_*.json files found"**.
972
978
  - **"Google Calendar API: HTTP 403"** with a hint about enabling the API.
973
979
  - **"Google Calendar API: HTTP 403 — Request had insufficient authentication scopes"** when pushing
974
980
  a collection to a calendar that is NOT in the account's own calendar list.
@@ -1121,8 +1127,7 @@ Pass the file instead. `putItems` accepts **`itemsFile`** — an absolute path t
1121
1127
  a JSON file holding the array of record objects, read by the host:
1122
1128
 
1123
1129
  ```jsonc
1124
- { "action": "putItems", "slug": "slots", "mode": "create",
1125
- "itemsFile": "/absolute/path/to/generated-slots.json" }
1130
+ { "action": "putItems", "slug": "slots", "mode": "create", "itemsFile": "/absolute/path/to/generated-slots.json" }
1126
1131
  ```
1127
1132
 
1128
1133
  Write the generated file **under the workspace**. Paths outside it are refused:
@@ -1144,17 +1149,17 @@ Rules that make it fail cleanly rather than silently:
1144
1149
 
1145
1150
  Reading the refusal you got:
1146
1151
 
1147
- | Message | What it means |
1148
- | --- | --- |
1149
- | `must be an ABSOLUTE path` | You passed a relative path. Pass the full one. |
1150
- | `must be inside the workspace` | The file is outside the workspace (or a symlink out of it). Regenerate it under the workspace. |
1151
- | `is a symbolic link` | Symlinks are never followed. Pass the real path. |
1152
- | `changed while it was being opened` | The file was replaced mid-call. Finish writing it, then call putItems. |
1153
- | `grew while it was being read` | The file was still being written. Wait for the script to finish, then call putItems. |
1154
- | `could not read \`itemsFile\`` | The host cannot see that path — the usual cause is a file written to a temp dir outside the mount. Write it under the workspace. |
1155
- | `is not a regular file` | The path is a directory, device, or fifo. |
1156
- | `could not be read as JSON` | The file exists and was read, but does not parse. This is YOUR file's shape, not a host problem — check the script that wrote it (a truncated write, a trailing comma, log output mixed into the file). |
1157
- | `must hold a non-empty JSON array of record objects` | It parsed, but is `[]`, an object, or an array of scalars. The file must be `[{…}, {…}]` — the same row objects you would have passed as `items`. |
1152
+ | Message | What it means |
1153
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1154
+ | `must be an ABSOLUTE path` | You passed a relative path. Pass the full one. |
1155
+ | `must be inside the workspace` | The file is outside the workspace (or a symlink out of it). Regenerate it under the workspace. |
1156
+ | `is a symbolic link` | Symlinks are never followed. Pass the real path. |
1157
+ | `changed while it was being opened` | The file was replaced mid-call. Finish writing it, then call putItems. |
1158
+ | `grew while it was being read` | The file was still being written. Wait for the script to finish, then call putItems. |
1159
+ | `could not read \`itemsFile\`` | The host cannot see that path — the usual cause is a file written to a temp dir outside the mount. Write it under the workspace. |
1160
+ | `is not a regular file` | The path is a directory, device, or fifo. |
1161
+ | `could not be read as JSON` | The file exists and was read, but does not parse. This is YOUR file's shape, not a host problem — check the script that wrote it (a truncated write, a trailing comma, log output mixed into the file). |
1162
+ | `must hold a non-empty JSON array of record objects` | It parsed, but is `[]`, an object, or an array of scalars. The file must be `[{…}, {…}]` — the same row objects you would have passed as `items`. |
1158
1163
 
1159
1164
  Do NOT spawn the MCP bridge yourself. A hand-written JSON-RPC client fails
1160
1165
  invisibly and can leave a partially written collection that nothing reports.
@@ -1196,10 +1201,14 @@ the calendar can place. So format the string, never convert it:
1196
1201
 
1197
1202
  ```js
1198
1203
  // WRONG — appends `Z` and shifts the hours by the generating machine's offset
1199
- { startAt: new Date(`${day}T08:00`).toISOString() } // "2026-08-17T15:00:00.000Z"
1204
+ {
1205
+ startAt: new Date(`${day}T08:00`).toISOString();
1206
+ } // "2026-08-17T15:00:00.000Z"
1200
1207
 
1201
1208
  // RIGHT — the clock you meant, written as the clock
1202
- { startAt: `${day}T08:00` } // "2026-08-17T08:00"
1209
+ {
1210
+ startAt: `${day}T08:00`;
1211
+ } // "2026-08-17T08:00"
1203
1212
  ```
1204
1213
 
1205
1214
  The conversion is the more expensive half: had the format passed, a Tokyo court's
@@ -1249,7 +1258,7 @@ second line, with the plugin-load errors above it, is what a bug report needs.
1249
1258
  ### Symptoms
1250
1259
 
1251
1260
  - Reading or writing a collection fails with `shared collection unavailable:
1252
- connect remote-host first`.
1261
+ connect remote-host first`.
1253
1262
  - A collection whose `schema.json` declares `"storage": { "type": "firestore" }`
1254
1263
  never appears in the list at all.
1255
1264
  - Reads and writes fail with `permission-denied` from Firestore even though the
@@ -1269,7 +1278,7 @@ The backend deliberately fails loudly instead of returning nothing.
1269
1278
 
1270
1279
  2. **The collection never appears** — discovery REFUSED the schema, and the
1271
1280
  reason is in the server log under `collections` (`schema.json rejected after
1272
- validation, skipping`). For a shared collection the usual reason is the app
1281
+ validation, skipping`). For a shared collection the usual reason is the app
1273
1282
  declaration: `apps/{aid}/collections/{cid}/items` needs an `aid`, which comes
1274
1283
  from `app.json` at the repository root, not from the schema.
1275
1284
 
@@ -1297,6 +1306,156 @@ nor remove documents that other members also read, and removing its records
1297
1306
  first does not unlock it. Retiring the whole app is a Firestore project
1298
1307
  administrator's recursive delete, not something this app does.
1299
1308
 
1309
+ ## A messaging bridge's bot does not reply, and nothing errors
1310
+
1311
+ ### Symptoms
1312
+
1313
+ The user talks to the bot on Telegram / Slack / LINE / Discord / …, and nothing
1314
+ comes back. No error in the server log, none from the bridge. Sending again does
1315
+ nothing either.
1316
+
1317
+ ### First, do NOT say "restart the bridge"
1318
+
1319
+ That advice is out of date. Since #3078 the shared client re-reads the token and
1320
+ the port after **every failed connection** and rebuilds its socket when the
1321
+ server has come back as a different generation. A server restart — new token,
1322
+ new port, or both — is handled without touching the bridge.
1323
+
1324
+ ### The bridge tells you where it went — start there
1325
+
1326
+ Every bridge prints its target as it starts — the shared client emits it, so
1327
+ this holds for all of them, not only the ones with a banner:
1328
+
1329
+ ```text
1330
+ Connecting to http://127.0.0.1:3099
1331
+ ```
1332
+
1333
+ It prints again each time the bridge follows the server to a new address, so a
1334
+ bridge that has outlived a restart has several. **Compare the last one** — the
1335
+ earlier lines are where it used to be. Against what the server published:
1336
+
1337
+ ```bash
1338
+ cat "${MULMOCLAUDE_WORKSPACE_PATH:-$HOME/mulmoclaude}/.server-port"
1339
+ ```
1340
+
1341
+ - **They agree** → the address is right; the problem is further in (see below).
1342
+ - **They disagree**, and the bridge says `http://localhost:3001` → that bridge
1343
+ is an npm build from before the port-following fix. Reinstall it
1344
+ (`npm i -g @mulmobridge/<platform>@latest`, or `npx @mulmobridge/<platform>@latest`).
1345
+ Check the banner rather than a version number: the banner is the behaviour,
1346
+ and a version is one more thing to keep current.
1347
+
1348
+ ### `The server has not published a port yet` is waiting, not failing
1349
+
1350
+ ```text
1351
+ The server has not published a port yet — waiting for it rather than guessing.
1352
+ ```
1353
+
1354
+ The server writes `.session-token` before it binds its port, and sandbox setup
1355
+ sits in between — a cold start that builds the Docker image can hold there for
1356
+ minutes. The bridge deliberately refuses to guess `localhost:3001` while holding
1357
+ a freshly minted token, and joins on its own once the port appears. Wait, or
1358
+ check that the server is actually starting.
1359
+
1360
+ ### A bridge that cannot see the workspace needs both variables
1361
+
1362
+ Running on another machine, or in a container without the workspace mounted,
1363
+ means no `.server-port` and no `.session-token`. Such a bridge needs BOTH:
1364
+
1365
+ ```bash
1366
+ MULMOCLAUDE_API_URL=http://127.0.0.1:<port> \
1367
+ MULMOCLAUDE_AUTH_TOKEN=<the same value the server was given> \
1368
+ npx @mulmobridge/<platform>
1369
+ ```
1370
+
1371
+ Two things make this narrower than it looks:
1372
+
1373
+ - **The server binds the IPv4 loopback only** (`app.listen(port, "127.0.0.1")`),
1374
+ so a bridge on another machine cannot reach it by naming the host. It needs a
1375
+ tunnel — an SSH port-forward is the usual one — and then
1376
+ `MULMOCLAUDE_API_URL` points at the LOCAL end of that tunnel, which is why the
1377
+ recipe above still says `127.0.0.1`. Do not "fix" this by making the server
1378
+ listen on `0.0.0.0`: there is no TLS on that port, and the bearer token would
1379
+ cross the network in the clear.
1380
+ - **`MULMOCLAUDE_AUTH_TOKEN` must be set on the SERVER too**, or it regenerates a
1381
+ new token on every start and the pinned one stops matching.
1382
+
1383
+ In a container on the same host, `MULMOCLAUDE_HOST=host.docker.internal` is how
1384
+ the sandbox's own hooks reach the parent server; a bridge there needs the same
1385
+ treatment, and the workspace mounted if you want it to follow the port.
1386
+
1387
+ ### Only then, the platform side
1388
+
1389
+ - The chat ID is not on the bridge's allowlist — the bot answers
1390
+ `"Access denied"` to a stranger, but an allowlist typo simply drops the
1391
+ message.
1392
+ - The platform token (bot token, app token) was revoked or regenerated.
1393
+ - The bridge process is not running at all. Check before assuming anything above.
1394
+ Since #3084 a bridge that died says why on its last line — `[<transport>]
1395
+ unhandled rejection — exiting: …` or `[<transport>] uncaught exception —
1396
+ exiting: …`, naming the transport. A bridge stopped on purpose names the
1397
+ signal: Ctrl-C prints `[<transport>] SIGINT — shutting down`, while a plain
1398
+ `kill <pid>` sends SIGTERM and prints `[<transport>] SIGTERM — shutting down`.
1399
+ **No such line and the process gone** means either an older npm build (they
1400
+ had no handlers at all, so a missed `await` left only a stack trace) or a kill
1401
+ no handler can catch (`kill -9` / SIGKILL, or the OOM killer).
1402
+
1403
+ ### What to collect if none of it explains the silence
1404
+
1405
+ The bridge's first three lines (they name the transport and the resolved URL),
1406
+ the server's `[server] listening port=…` line, and whether
1407
+ `<workspace>/.server-port` exists. Those three answer "which server, on which
1408
+ port, and did the bridge agree" — which is what every one of these turns out to
1409
+ be.
1410
+
1411
+ ## A webhook bridge exits at startup, or never binds its port
1412
+
1413
+ ### Symptoms
1414
+
1415
+ One of the webhook bridges — `line`, `line-works`, `google-chat`, `messenger`,
1416
+ `teams`, `twilio-sms`, `viber`, `webhook`, `whatsapp` — exits immediately after
1417
+ being started, printing one of:
1418
+
1419
+ ```text
1420
+ Port 3002 is already in use. Set LINE_BRIDGE_PORT to a free port, or LINE_BRIDGE_PORT=0 to let the OS pick one.
1421
+ LINE_BRIDGE_PORT="302a" is not a usable port. Set it to an integer from 0 to 65535 (LINE_BRIDGE_PORT=0 asks the OS for a free port), or unset it to use 3002.
1422
+ ```
1423
+
1424
+ Both name the env var to change, and each bridge has its own — `WEBHOOK_PORT`,
1425
+ `TWILIO_WEBHOOK_PORT`, `VIBER_WEBHOOK_PORT` and so on. Read the message rather
1426
+ than guessing the name.
1427
+
1428
+ ### "Already in use" is usually the server, not a second bridge
1429
+
1430
+ The server's port walk (3002-3021) covers the whole bridge band (3002-3013), so
1431
+ a server that found 3001 busy and moved forward can be sitting on a bridge's
1432
+ default. Check what the server actually bound before moving the bridge:
1433
+
1434
+ ```bash
1435
+ cat "${MULMOCLAUDE_WORKSPACE_PATH:-$HOME/mulmoclaude}/.server-port"
1436
+ lsof -nP -iTCP:3002 -sTCP:LISTEN
1437
+ ```
1438
+
1439
+ Two ways out, both fine:
1440
+
1441
+ - Give the bridge a port outside the server's band: `LINE_BRIDGE_PORT=3200`.
1442
+ - Let the OS choose: `LINE_BRIDGE_PORT=0`. The startup banner then names the
1443
+ port it actually got — read the banner, not the env var, when tunnelling to it
1444
+ (`Webhook listening on http://localhost:52431/webhook`).
1445
+
1446
+ ### Before #3084 both of these were silent
1447
+
1448
+ A bridge of that vintage reads its port as `Number(process.env.X) || <default>`
1449
+ and calls a bare `app.listen`. So a **typo ran on the default without a word**
1450
+ (`302a` → `NaN` → falsy → the default), `X=0` was impossible (falsy too), and a
1451
+ busy port surfaced as an unhandled `EADDRINUSE` with no mention of which env var
1452
+ to change. If neither message above appears and the bridge simply dies, or it is
1453
+ answering on a port you did not ask for, that is the old build: reinstall it
1454
+ (`npm i -g @mulmobridge/<platform>@latest`).
1455
+
1456
+ A bridge that starts but never replies is a different failure — see "A messaging
1457
+ bridge's bot does not reply, and nothing errors" above.
1458
+
1300
1459
  ## `renderShapeScript` says Chromium is not installed
1301
1460
 
1302
1461
  `renderShapeScript` rasterises a ShapeScript model by driving Puppeteer's
@@ -1326,3 +1485,52 @@ The same message with `(launch failed: …)` appended means the browser is
1326
1485
  installed but would not start — usually a sandbox with no permission to
1327
1486
  spawn it. Report the parenthesised reason to the user rather than the
1328
1487
  install hint alone.
1488
+
1489
+ ## The server refuses to start — "MulmoClaude is already running against this workspace"
1490
+
1491
+ ### Symptoms
1492
+
1493
+ - `yarn dev`, `yarn server` or `npx mulmoclaude` exits immediately with:
1494
+ `MulmoClaude is already running against this workspace at http://localhost:<port>`
1495
+ - It happens even with a free port named explicitly (`PORT=3100 yarn dev`).
1496
+
1497
+ ### Why
1498
+
1499
+ Two servers over one workspace overwrite each other's `.session-token`. After
1500
+ that a stateless plugin dispatch authenticates cleanly against the *wrong*
1501
+ server, while the session-scoped `/api/internal/tool-result` push lands where
1502
+ the session does not exist and is dropped — so plugin views simply never render
1503
+ on one of the two and nothing reports an error. The guard refuses that setup
1504
+ rather than letting it happen quietly (#3079).
1505
+
1506
+ The check reads `<workspace>/.server-port` and probes the port it names, so it
1507
+ is about the WORKSPACE, not the port: a busy 3001 held by some other program
1508
+ still walks forward as before.
1509
+
1510
+ ### Fix
1511
+
1512
+ Usually the message is right and there is already a server to use — open the URL
1513
+ it printed.
1514
+
1515
+ To run a genuinely separate second instance, give it its own workspace (no flag
1516
+ needed, this is the supported shape):
1517
+
1518
+ ```bash
1519
+ MULMOCLAUDE_WORKSPACE_PATH=~/mulmoclaude-scratch PORT=3100 yarn dev
1520
+ ```
1521
+
1522
+ If the first server was killed hard (`kill -9`, a crashed container) the sidecar
1523
+ can name a port something else has since taken; the probe treats a non-MulmoClaude
1524
+ answer as "nobody there", so that case does not stop a launch. A stop therefore
1525
+ means a real instance answered.
1526
+
1527
+ To share one workspace between two servers anyway — accepting the token stomping:
1528
+
1529
+ ```bash
1530
+ MULMOCLAUDE_ALLOW_MULTIPLE_INSTANCES=1 yarn dev
1531
+ ```
1532
+
1533
+ Use the env var for `yarn dev`. The `--allow-multiple-instances` flag works on
1534
+ `npx mulmoclaude` and `yarn server`, but **not** on `yarn dev`: that is a compound
1535
+ `a && b && c` script, yarn appends extra args to the last command only, and the
1536
+ guard that stops the launch runs in the first one.
@@ -67,7 +67,7 @@ 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 in the sandbox, Marp PDF, registry import, build/workspace, plugin runtime), plus the four-step triage for when a user reports something broken
70
+ - [Error recovery](config/helps/error-recovery.md) — the lookup the agent reads on tool failures (sandbox gh/git/SSH, Marp PDF, registry import, build/workspace, plugin runtime, a bridge gone quiet), plus the triage for a “broken” report
71
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
72
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
73
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
@@ -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. 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.
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 — `npx mulmoclaude --disable-sandbox` or `yarn server --disable-sandbox` (**not** `yarn dev`, whose compound script drops trailing args; use the env var there). 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. 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.
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 `npx mulmoclaude` and `yarn server` — but **not** `yarn dev`, which drops trailing args — 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.
@@ -7,7 +7,7 @@ This is useful when you want to reach your MulmoClaude away from your computer
7
7
  ## How It Works
8
8
 
9
9
  - You create a **bot** with Telegram's BotFather; it gives you a token.
10
- - You run a **bridge process** (`yarn telegram`) on the same machine as the MulmoClaude server. The bridge uses your bot token to receive messages from Telegram, forwards them to MulmoClaude over `localhost:3001`, and sends the replies back to the Telegram user.
10
+ - You run a **bridge process** (`yarn telegram`) on the same machine as the MulmoClaude server. The bridge uses your bot token to receive messages from Telegram, forwards them to MulmoClaude over the loopback port the server actually bound, and sends the replies back to the Telegram user. It finds that port itself, and follows it when the server restarts.
11
11
  - A short **allowlist** of Telegram chat IDs controls who can talk to the bot. Everyone else gets `"Access denied"`.
12
12
 
13
13
  Your computer has to be on and connected to the internet for the bot to respond. Close the laptop → the bot goes silent.
@@ -41,7 +41,7 @@ In terminal A, start MulmoClaude:
41
41
  yarn dev
42
42
  ```
43
43
 
44
- Wait until you see `[server] listening port=3001`.
44
+ Wait until you see `[server] listening port=…`. The number is whatever the server bound: it honours `PORT`, and an implicit default that is already busy walks forward. You do not need to note it — the bridge reads it from the workspace.
45
45
 
46
46
  In terminal B, start the bridge. Leave the allowlist **empty on purpose** for the first run — you will need to discover your own chat ID before you can add it.
47
47
 
@@ -56,9 +56,15 @@ Expected output:
56
56
  ```
57
57
  MulmoClaude Telegram bridge
58
58
  Allowlist: (empty — all chats will be denied)
59
+ Connecting to http://127.0.0.1:<port>
59
60
  Connected (<socket id>).
60
61
  ```
61
62
 
63
+ That third line is the bridge telling you which server it found. If it ever says
64
+ `http://localhost:3001` while `.server-port` says something else, that bridge is
65
+ an old build — see "a messaging bridge's bot does not reply" in the error
66
+ recovery help.
67
+
62
68
  ## Step 3 — Find Your Chat ID and Allowlist It
63
69
 
64
70
  1. In Telegram, open your new bot (search the username you picked) and send it any message — `hi` works.
@@ -114,7 +120,7 @@ Any other text is treated as a message to the assistant.
114
120
 
115
121
  ## Troubleshooting
116
122
 
117
- **`Connect error: bearer token rejected`** — MulmoClaude was restarted, so its bearer token changed. Restart `yarn telegram` to pick up the new one. To avoid this, pin `MULMOCLAUDE_AUTH_TOKEN` to the same value on both sides (see `docs/developer.md` §Auth).
123
+ **`Connect error: bearer token rejected`** — MulmoClaude restarted and its bearer token changed. **Do not restart the bridge**: it re-reads the token and the port after a failed connection and reconnects on its own, usually within a second or two. If it is still saying this after that, the server has not finished starting (it writes the token before it binds its port, and a cold start builds the sandbox image in between) — wait for `[server] listening port=…`. Pinning `MULMOCLAUDE_AUTH_TOKEN` on both sides is for a bridge that cannot read the workspace at all, not for this.
118
124
 
119
125
  **`TELEGRAM_ALLOWED_CHAT_IDS: "foo" is not an integer chat id`** — typo in the allowlist. Chat IDs are plain integers only — no spaces, quotes, or `#` prefix. Negative integers (for group chats) are allowed.
120
126
 
@@ -129,7 +135,7 @@ Any other text is treated as a message to the assistant.
129
135
  - The bot token is a password. If it leaks, regenerate it via BotFather's `/revoke`.
130
136
  - The allowlist is the only thing standing between "my friends" and "every Telegram user on Earth". Keep it current — remove chat IDs when you no longer want that person to have access, and restart the bridge.
131
137
  - The bridge logs chat IDs, usernames, and message lengths, but **not** message contents or the bot token. If you need a full audit trail, record it separately.
132
- - The MulmoClaude bearer token never leaves your machine. The bridge only talks to `localhost:3001`; your friends talk to Telegram's servers, which then talk to your bridge.
138
+ - The MulmoClaude bearer token never leaves your machine. The bridge only ever talks to the IPv4 loopback (`127.0.0.1`), whatever port the server bound; your friends talk to Telegram's servers, which then talk to your bridge.
133
139
 
134
140
  ## Full Operator Guide
135
141
 
@@ -1,9 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("../dist-CsgSfWwR.cjs");
2
+ const require_dist = require("../dist-Gj7ygaW2.cjs");
3
3
  const require_itemId = require("../itemId-CGT2J7YK.cjs");
4
- const require_promptSafety = require("../promptSafety-Cv-SoTd4.cjs");
4
+ const require_promptSafety = require("../promptSafety-caJWd1ZQ.cjs");
5
5
  const require_project = require("../project-C1ep9pvo.cjs");
6
6
  const require_iconGlyph = require("../iconGlyph-fSBp6k4J.cjs");
7
+ require("../errorMessage-CT-va_Pv.cjs");
7
8
  //#region src/collection/core/presentCollection.ts
8
9
  var TOOL_NAME = "presentCollection";
9
10
  /** Stamp a host's opaque project scope onto a card payload. The ONLY way a