@mulmoclaude/core 3.0.0 → 3.1.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.
Files changed (77) hide show
  1. package/assets/helps/collection-skills.md +72 -8
  2. package/assets/helps/custom-view.md +9 -1
  3. package/assets/helps/error-recovery.md +36 -0
  4. package/assets/helps/feeds.md +6 -4
  5. package/dist/calendarGrid-Csy2rpjp.js.map +1 -1
  6. package/dist/calendarGrid-p8K-7cuB.cjs.map +1 -1
  7. package/dist/collection/core/presentCollection.d.ts +49 -0
  8. package/dist/collection/core/schema.d.ts +16 -1
  9. package/dist/collection/index.cjs +56 -7
  10. package/dist/collection/index.cjs.map +1 -1
  11. package/dist/collection/index.js +53 -8
  12. package/dist/collection/index.js.map +1 -1
  13. package/dist/collection/registry/server/importWriter.d.ts +2 -1
  14. package/dist/collection/registry/server/index.cjs +31 -10
  15. package/dist/collection/registry/server/index.cjs.map +1 -1
  16. package/dist/collection/registry/server/index.js +30 -9
  17. package/dist/collection/registry/server/index.js.map +1 -1
  18. package/dist/collection/server/host.d.ts +34 -4
  19. package/dist/collection/server/index.cjs +4 -2
  20. package/dist/collection/server/index.d.ts +1 -1
  21. package/dist/collection/server/index.js +4 -3
  22. package/dist/collection/server/manageTool.d.ts +19 -0
  23. package/dist/collection/server/schemaDocs.d.ts +5 -1
  24. package/dist/collection-watchers/config.d.ts +15 -2
  25. package/dist/collection-watchers/index.cjs +310 -198
  26. package/dist/collection-watchers/index.cjs.map +1 -1
  27. package/dist/collection-watchers/index.js +310 -198
  28. package/dist/collection-watchers/index.js.map +1 -1
  29. package/dist/collection-watchers/reconciler.d.ts +1 -5
  30. package/dist/collection-watchers/watcher.d.ts +31 -26
  31. package/dist/{discovery-4Z-ZWFam.js → discovery-CC7kuwHf.js} +21 -7
  32. package/dist/discovery-CC7kuwHf.js.map +1 -0
  33. package/dist/{discovery-CtiZMlsX.cjs → discovery-CDOjfctD.cjs} +33 -7
  34. package/dist/discovery-CDOjfctD.cjs.map +1 -0
  35. package/dist/feeds/server/index.cjs +26 -6
  36. package/dist/feeds/server/index.cjs.map +1 -1
  37. package/dist/feeds/server/index.d.ts +1 -1
  38. package/dist/feeds/server/index.js +26 -7
  39. package/dist/feeds/server/index.js.map +1 -1
  40. package/dist/feeds/server/scheduledRefresh.d.ts +16 -1
  41. package/dist/files/index.cjs +6 -5
  42. package/dist/files/index.cjs.map +1 -1
  43. package/dist/files/index.d.ts +1 -0
  44. package/dist/files/index.js +2 -2
  45. package/dist/files/root.d.ts +15 -0
  46. package/dist/google/index.cjs +3 -3
  47. package/dist/google/index.cjs.map +1 -1
  48. package/dist/google/index.js +2 -2
  49. package/dist/remote-host/index.cjs +3 -1
  50. package/dist/remote-host/index.d.ts +30 -0
  51. package/dist/remote-host/index.js +2 -2
  52. package/dist/remote-host/server/index.cjs +38 -14
  53. package/dist/remote-host/server/index.cjs.map +1 -1
  54. package/dist/remote-host/server/index.js +38 -14
  55. package/dist/remote-host/server/index.js.map +1 -1
  56. package/dist/remote-host/server/monotonicClock.d.ts +3 -0
  57. package/dist/remote-host/server/presenceBeat.d.ts +3 -2
  58. package/dist/remote-host/server/resilientRunner.d.ts +6 -0
  59. package/dist/{remote-host-Dba4lF3Z.js → remote-host-CFnl1I-L.js} +35 -2
  60. package/dist/{remote-host-Dba4lF3Z.js.map → remote-host-CFnl1I-L.js.map} +1 -1
  61. package/dist/{remote-host-CtQjagPt.cjs → remote-host-EQPlccB9.cjs} +46 -1
  62. package/dist/{remote-host-CtQjagPt.cjs.map → remote-host-EQPlccB9.cjs.map} +1 -1
  63. package/dist/{atomic-DPpdrJzO.js → root-BMroU_mB.js} +21 -2
  64. package/dist/root-BMroU_mB.js.map +1 -0
  65. package/dist/{atomic-DhUk8uiM.cjs → root-rPH6FGDT.cjs} +26 -1
  66. package/dist/root-rPH6FGDT.cjs.map +1 -0
  67. package/dist/{server-CedIocRS.js → server-AA2kfDfk.js} +137 -42
  68. package/dist/server-AA2kfDfk.js.map +1 -0
  69. package/dist/{server-DOZFzx5l.cjs → server-WU35UYlF.cjs} +138 -43
  70. package/dist/server-WU35UYlF.cjs.map +1 -0
  71. package/package.json +1 -1
  72. package/dist/atomic-DPpdrJzO.js.map +0 -1
  73. package/dist/atomic-DhUk8uiM.cjs.map +0 -1
  74. package/dist/discovery-4Z-ZWFam.js.map +0 -1
  75. package/dist/discovery-CtiZMlsX.cjs.map +0 -1
  76. package/dist/server-CedIocRS.js.map +0 -1
  77. package/dist/server-DOZFzx5l.cjs.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { i as hasStringProp, t as errorMessage } from "../dist-D8zokgGo.js";
2
2
  import { t as truncate } from "../text-CjDbP2O1.js";
3
- import { t as writeFileAtomic } from "../atomic-DPpdrJzO.js";
3
+ import { n as writeFileAtomic } from "../root-BMroU_mB.js";
4
4
  import { fetchWithTimeout } from "../utils/fetch.js";
5
5
  import { l as parseIsoDate, u as parseIsoDateTime } from "../calendarGrid-Csy2rpjp.js";
6
6
  import { t as mergeIntoExisting } from "../project-B0j_Vf_Q.js";
7
- import { Y as getWorkspaceRoot, d as storeFor, n as discoverCollections, nt as createForwardingLogger, rt as createHostSlot } from "../discovery-4Z-ZWFam.js";
7
+ import { Y as getWorkspaceRoot, at as createHostSlot, d as storeFor, it as createForwardingLogger, n as discoverCollections } from "../discovery-CC7kuwHf.js";
8
8
  import { t as mulmoConfigDir } from "../paths-CkJxNZ7T.js";
9
9
  import { promises } from "node:fs";
10
10
  import path, { join } from "node:path";
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_remote_host = require("../remote-host-CtQjagPt.cjs");
2
+ const require_remote_host = require("../remote-host-EQPlccB9.cjs");
3
+ exports.COMMAND_SCOPE_PARAM = require_remote_host.COMMAND_SCOPE_PARAM;
3
4
  exports.REMOTE_HOST_PROTOCOL_VERSION = require_remote_host.REMOTE_HOST_PROTOCOL_VERSION;
4
5
  exports.RUNNER_HEALTH_STATES = require_remote_host.RUNNER_HEALTH_STATES;
5
6
  exports.buildHostPresence = require_remote_host.buildHostPresence;
@@ -10,4 +11,5 @@ exports.hostDoc = require_remote_host.hostDoc;
10
11
  exports.isExpired = require_remote_host.isExpired;
11
12
  exports.isRunnerHealth = require_remote_host.isRunnerHealth;
12
13
  exports.isRunnerHealthState = require_remote_host.isRunnerHealthState;
14
+ exports.readCommandScope = require_remote_host.readCommandScope;
13
15
  exports.toJsonObject = require_remote_host.toJsonObject;
@@ -58,6 +58,36 @@ export declare const isExpired: (command: Pick<Command, "expiresAt">, now: numbe
58
58
  export declare const byCreatedAt: (left: Pick<Command, "createdAt">, right: Pick<Command, "createdAt">) => number;
59
59
  export type CommandHandler = (params: JsonObject) => JsonValue | Promise<JsonValue>;
60
60
  export type CommandHandlers = Record<string, CommandHandler>;
61
+ /** The param name a collection-serving command uses to say WHICH project's
62
+ * collections it means. Reserved and documented now, before a phone client
63
+ * ships, because the parts that are hard to change later are the ones being
64
+ * written today. Four rules go with it:
65
+ *
66
+ * 1. **It is an OPAQUE scope, never a path.** The phone is a genuinely remote
67
+ * client; an absolute root in a command, an artifact or a token publishes
68
+ * the user's home directory over the wire. Mint a project id host-side and
69
+ * resolve it host-side.
70
+ * 2. **The phone must be able to LEARN the list.** A picker needs
71
+ * `{ id, label }` pairs from the host — a command of its own, or a field on
72
+ * an existing listing. Designing the scope value now is what keeps that the
73
+ * ONLY new thing when the feature lands.
74
+ * 3. **Handlers RESOLVE a scope; they do not hard-code one.** Write each
75
+ * collection handler as "read the scope from params, defaulting to the
76
+ * host's root" rather than calling the workspace accessor inline. Today
77
+ * every call resolves the default and behaves exactly as it does now; the
78
+ * day the param arrives, no handler changes.
79
+ * 4. **The artifact stays host-built.** A remote view's srcdoc, its inlined
80
+ * image thumbnails and its token are assembled on the host, so the phone
81
+ * never resolves a path itself. That is what makes (1) hold without
82
+ * trusting the client. */
83
+ export declare const COMMAND_SCOPE_PARAM = "project";
84
+ /** Read the opaque project scope off a command's params, or `undefined` for
85
+ * "the host's own root" — which is what every command means today. A
86
+ * non-string (or empty) value is treated as absent rather than as an error: a
87
+ * scope the host cannot resolve must fall back to the default, never to a
88
+ * guess. Hosts pass the result to their own id → root resolver; this module
89
+ * deliberately never sees a path. */
90
+ export declare const readCommandScope: (params: JsonObject) => string | undefined;
61
91
  export declare const REMOTE_HOST_PROTOCOL_VERSION = 2;
62
92
  export interface HostPresence {
63
93
  online: boolean;
@@ -1,2 +1,2 @@
1
- import { a as commandsCollection, c as toJsonObject, d as isRunnerHealthState, i as coerceJsonObject, l as RUNNER_HEALTH_STATES, n as buildHostPresence, o as hostDoc, r as byCreatedAt, s as isExpired, t as REMOTE_HOST_PROTOCOL_VERSION, u as isRunnerHealth } from "../remote-host-Dba4lF3Z.js";
2
- export { REMOTE_HOST_PROTOCOL_VERSION, RUNNER_HEALTH_STATES, buildHostPresence, byCreatedAt, coerceJsonObject, commandsCollection, hostDoc, isExpired, isRunnerHealth, isRunnerHealthState, toJsonObject };
1
+ import { a as coerceJsonObject, c as isExpired, d as RUNNER_HEALTH_STATES, f as isRunnerHealth, i as byCreatedAt, l as readCommandScope, n as REMOTE_HOST_PROTOCOL_VERSION, o as commandsCollection, p as isRunnerHealthState, r as buildHostPresence, s as hostDoc, t as COMMAND_SCOPE_PARAM, u as toJsonObject } from "../remote-host-CFnl1I-L.js";
2
+ export { COMMAND_SCOPE_PARAM, REMOTE_HOST_PROTOCOL_VERSION, RUNNER_HEALTH_STATES, buildHostPresence, byCreatedAt, coerceJsonObject, commandsCollection, hostDoc, isExpired, isRunnerHealth, isRunnerHealthState, readCommandScope, toJsonObject };
@@ -1,7 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_dist = require("../../dist-CsgSfWwR.cjs");
3
- const require_remote_host = require("../../remote-host-CtQjagPt.cjs");
3
+ const require_remote_host = require("../../remote-host-EQPlccB9.cjs");
4
4
  let firebase_firestore = require("firebase/firestore");
5
+ let node_perf_hooks = require("node:perf_hooks");
5
6
  let firebase_auth = require("firebase/auth");
6
7
  let firebase_app = require("firebase/app");
7
8
  let firebase_storage = require("firebase/storage");
@@ -61,6 +62,11 @@ var matchesPathPattern = (path, pattern) => {
61
62
  */
62
63
  var unexpectedPaths = (paths, expected = []) => paths.filter((path) => !expected.some((pattern) => matchesPathPattern(path, pattern)));
63
64
  //#endregion
65
+ //#region src/remote-host/server/monotonicClock.ts
66
+ /** Milliseconds from an arbitrary origin, never moving backwards. Only differences
67
+ * between two readings mean anything — never compare one to a wall-clock time. */
68
+ var monotonicNowMs = () => node_perf_hooks.performance.now();
69
+ //#endregion
64
70
  //#region src/remote-host/server/presenceBeat.ts
65
71
  var PRESENCE_STALE_BEATS = 3;
66
72
  var errorText$1 = (error) => error instanceof Error ? error.message : String(error);
@@ -72,21 +78,25 @@ var notify = (report, onThrow) => {
72
78
  }
73
79
  };
74
80
  var createPresenceBeat = (deps) => {
75
- const now = deps.now ?? Date.now;
76
- const state = { lastAckMs: now() };
81
+ const now = deps.now ?? monotonicNowMs;
82
+ const state = {
83
+ lastAckMs: now(),
84
+ beatsSinceAck: 0
85
+ };
77
86
  const announce = (online) => {
78
87
  const fail = (error) => notify(() => deps.onError(errorText$1(error)));
79
88
  const ack = () => notify(() => {
80
89
  state.lastAckMs = now();
90
+ state.beatsSinceAck = 0;
81
91
  });
82
92
  notify(() => {
83
93
  deps.write(online).then(ack, fail);
84
94
  }, fail);
85
95
  };
86
96
  const beat = () => {
87
- const silentMs = now() - state.lastAckMs;
88
- if (silentMs >= deps.staleAfterMs) {
89
- notify(() => deps.onStale(silentMs));
97
+ state.beatsSinceAck += 1;
98
+ if (state.beatsSinceAck >= deps.staleAfterBeats) {
99
+ notify(() => deps.onStale(now() - state.lastAckMs));
90
100
  return;
91
101
  }
92
102
  announce(true);
@@ -241,7 +251,7 @@ function handleListenError(run, error) {
241
251
  message: error.message
242
252
  });
243
253
  if (run.stopped) return;
244
- const now = Date.now();
254
+ const now = monotonicNowMs();
245
255
  run.downSinceMs ??= now;
246
256
  if (classifyListenerError(error) === "fatal" || shouldGiveUpListening(run.downSinceMs, now)) {
247
257
  run.goOffline();
@@ -292,10 +302,10 @@ var buildPresenceBeat = (firestore, channel, handlers, options, onStale) => {
292
302
  }),
293
303
  onError: (message) => report(`presence write failed: ${message}`),
294
304
  onStale: (silentMs) => {
295
- report(`no presence write acknowledged for ${Math.round(silentMs / 1e3)}s — the remote cannot see this host`);
305
+ report(`no presence write acknowledged across 3 beats (${Math.round(silentMs / 1e3)}s) — the remote cannot see this host`);
296
306
  onStale();
297
307
  },
298
- staleAfterMs: presenceStaleAfterMs(options)
308
+ staleAfterBeats: 3
299
309
  });
300
310
  };
301
311
  var shutDown = (run, presence) => {
@@ -337,6 +347,7 @@ var RECONNECT_MAX_MS = ONE_MINUTE_MS;
337
347
  var SETTLE_MS = ONE_MINUTE_MS;
338
348
  var GIVE_UP_MS = 5 * ONE_MINUTE_MS;
339
349
  var PROBE_INTERVAL_MS = 90 * ONE_SECOND_MS$1;
350
+ var RESUME_GAP_MS = ONE_MINUTE_MS;
340
351
  var reconnectDelayMs = (attempt) => Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** attempt);
341
352
  var errorText = (error) => error instanceof Error ? error.message : String(error);
342
353
  var scheduleWithTimeout = (task, delayMs) => {
@@ -349,7 +360,7 @@ var setState = (ctx, next) => {
349
360
  ctx.deps.onHealth?.({
350
361
  state: next,
351
362
  lastError: ctx.lastError,
352
- changedAt: ctx.now()
363
+ changedAt: ctx.wallNow()
353
364
  });
354
365
  } catch (error) {
355
366
  ctx.deps.log.warn(`host runner health observer threw: ${errorText(error)}`);
@@ -363,10 +374,22 @@ var clearProbe = (ctx) => {
363
374
  ctx.cancelProbe?.();
364
375
  ctx.cancelProbe = null;
365
376
  };
377
+ var after = (ctx, delayMs, task) => {
378
+ const dueMs = ctx.now() + delayMs;
379
+ return ctx.schedule(() => {
380
+ const overshootMs = ctx.now() - dueMs;
381
+ if (overshootMs > RESUME_GAP_MS) {
382
+ ctx.deps.log.warn(`host runner resumed after a ${Math.round(overshootMs / ONE_SECOND_MS$1)}s gap; that time does not count as outage`);
383
+ ctx.downSinceMs = null;
384
+ ctx.attempt = 0;
385
+ }
386
+ task();
387
+ }, delayMs);
388
+ };
366
389
  function scheduleProbe(ctx) {
367
390
  if (ctx.stopped || !ctx.deps.checkAlive) return;
368
391
  clearProbe(ctx);
369
- ctx.cancelProbe = ctx.schedule(() => void runProbe(ctx), PROBE_INTERVAL_MS);
392
+ ctx.cancelProbe = after(ctx, PROBE_INTERVAL_MS, () => void runProbe(ctx));
370
393
  }
371
394
  var STALE_REASON = "presence went stale — the phone can no longer see this host";
372
395
  async function askAlive(ctx) {
@@ -438,7 +461,7 @@ function scheduleRelaunch(ctx) {
438
461
  ctx.attempt += 1;
439
462
  ctx.deps.log.warn(`host runner closed (${ctx.lastError ?? "no error reported"}); re-subscribing in ${Math.round(delayMs / ONE_SECOND_MS$1)}s`);
440
463
  if (ctx.state !== "reconnecting") setState(ctx, "reconnecting");
441
- ctx.cancelTimer = ctx.schedule(() => launch(ctx), delayMs);
464
+ ctx.cancelTimer = after(ctx, delayMs, () => launch(ctx));
442
465
  }
443
466
  function onUnderlyingClosed(ctx) {
444
467
  if (ctx.stopped) return;
@@ -472,13 +495,14 @@ function launch(ctx) {
472
495
  onUnderlyingClosed(ctx);
473
496
  return;
474
497
  }
475
- ctx.cancelTimer = ctx.schedule(() => void settle(ctx), SETTLE_MS);
498
+ ctx.cancelTimer = after(ctx, SETTLE_MS, () => void settle(ctx));
476
499
  }
477
500
  function startResilientHostRunner(deps) {
478
501
  const ctx = {
479
502
  deps,
480
503
  schedule: deps.schedule ?? scheduleWithTimeout,
481
- now: deps.now ?? Date.now,
504
+ now: deps.now ?? monotonicNowMs,
505
+ wallNow: deps.wallNow ?? Date.now,
482
506
  stopUnderlying: null,
483
507
  cancelTimer: null,
484
508
  attempt: 0,