@mulmoclaude/core 1.9.0 → 1.11.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 (49) hide show
  1. package/assets/helps/error-recovery.md +85 -0
  2. package/assets/helps/google-calendar-collection.md +41 -18
  3. package/dist/collection/core/schemaZ.d.ts +10 -1
  4. package/dist/collection/registry/server/index.cjs +2 -2
  5. package/dist/collection/registry/server/index.js +2 -2
  6. package/dist/collection/server/index.cjs +2 -2
  7. package/dist/collection/server/index.js +2 -2
  8. package/dist/collection-watchers/index.cjs +2 -2
  9. package/dist/collection-watchers/index.js +2 -2
  10. package/dist/{discovery-_fjsluLx.js → discovery-B4CZvrXR.js} +11 -3
  11. package/dist/{discovery-_fjsluLx.js.map → discovery-B4CZvrXR.js.map} +1 -1
  12. package/dist/{discovery-DYcR83qx.cjs → discovery-NRy3tyUA.cjs} +11 -3
  13. package/dist/{discovery-DYcR83qx.cjs.map → discovery-NRy3tyUA.cjs.map} +1 -1
  14. package/dist/feeds/server/index.cjs +2 -2
  15. package/dist/feeds/server/index.js +2 -2
  16. package/dist/google/calendar.d.ts +9 -0
  17. package/dist/google/calendarLock.d.ts +16 -0
  18. package/dist/google/calendarPushState.d.ts +1 -1
  19. package/dist/google/collectionProjection.d.ts +18 -0
  20. package/dist/google/collectionPush.d.ts +62 -0
  21. package/dist/google/collectionSync.d.ts +51 -23
  22. package/dist/google/index.cjs +833 -567
  23. package/dist/google/index.cjs.map +1 -1
  24. package/dist/google/index.d.ts +4 -2
  25. package/dist/google/index.js +825 -568
  26. package/dist/google/index.js.map +1 -1
  27. package/dist/google/pushPlan.d.ts +1 -1
  28. package/dist/remote-host/health.d.ts +13 -0
  29. package/dist/remote-host/index.cjs +11 -30
  30. package/dist/remote-host/index.d.ts +2 -0
  31. package/dist/remote-host/index.js +2 -24
  32. package/dist/remote-host/server/index.cjs +223 -8
  33. package/dist/remote-host/server/index.cjs.map +1 -1
  34. package/dist/remote-host/server/index.d.ts +4 -0
  35. package/dist/remote-host/server/index.js +214 -5
  36. package/dist/remote-host/server/index.js.map +1 -1
  37. package/dist/remote-host/server/presenceProbe.d.ts +23 -0
  38. package/dist/remote-host/server/resilientRunner.d.ts +24 -0
  39. package/dist/remote-host-D_BRFHcI.js +36 -0
  40. package/dist/remote-host-D_BRFHcI.js.map +1 -0
  41. package/dist/remote-host-DkDVxNim.cjs +95 -0
  42. package/dist/remote-host-DkDVxNim.cjs.map +1 -0
  43. package/dist/{server-l4TZ5Lbf.cjs → server-7U-3DE2e.cjs} +2 -2
  44. package/dist/{server-l4TZ5Lbf.cjs.map → server-7U-3DE2e.cjs.map} +1 -1
  45. package/dist/{server-BlIrtGKl.js → server-DdyIvFhz.js} +2 -2
  46. package/dist/{server-BlIrtGKl.js.map → server-DdyIvFhz.js.map} +1 -1
  47. package/package.json +5 -5
  48. package/dist/remote-host/index.cjs.map +0 -1
  49. package/dist/remote-host/index.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-host-DkDVxNim.cjs","names":[],"sources":["../src/remote-host/health.ts","../src/remote-host/index.ts"],"sourcesContent":["// Health of the remote-host command channel, as reported by the resilient runner\n// and rendered by a host's toolbar control. Browser-safe on purpose: the client\n// narrows the parsed HTTP payload with the same guard the server writes it from,\n// so the two sides cannot drift on the state names.\n//\n// online — the Firestore subscription is up; the phone can reach this host\n// reconnecting — it died and is being re-subscribed with backoff (self-healing)\n// offline — re-subscribing stopped helping, or nothing is connected at all;\n// recovering needs a re-auth from the browser's parked session\n//\n// Deliberately no UI wording here — how a state reads to a user is each host's\n// i18n, and core owning it would make the shared package a translation authority.\nexport const RUNNER_HEALTH_STATES = [\"online\", \"reconnecting\", \"offline\"] as const;\nexport type RunnerHealthState = (typeof RUNNER_HEALTH_STATES)[number];\n\nexport interface RunnerHealth {\n state: RunnerHealthState;\n /** Last channel error seen, for the popover and the log. Null before the first one. */\n lastError: string | null;\n /** ms epoch of the last state change, so the UI can say how long it has been down. */\n changedAt: number;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === \"object\" && value !== null;\n\nexport const isRunnerHealthState = (value: unknown): value is RunnerHealthState => RUNNER_HEALTH_STATES.some((state) => state === value);\n\n/** Narrows a parsed HTTP payload. The client renders whatever this accepts, so a\n * half-shaped health has to read as \"no health reported\" rather than as a state. */\nexport const isRunnerHealth = (value: unknown): value is RunnerHealth =>\n isRecord(value) &&\n isRunnerHealthState(value.state) &&\n (value.lastError === null || typeof value.lastError === \"string\") &&\n typeof value.changedAt === \"number\";\n","// Remote-host command-channel protocol — the browser-safe contract shared by a\n// host (MulmoClaude, MulmoTerminal) and the remote/mobile client (mulmoserver).\n//\n// A host signs in to Firebase as the user, listens to that user's per-host\n// command queue in Firestore, runs a handler, and writes the result back; the\n// remote writes commands and reads results via a real-time listener. This module\n// owns the wire types + the Firestore path helpers. It is the single source of\n// truth so the host runner and the client never drift on the protocol.\n//\n// Ported from ../mulmoserver/src/firestore/commandChannel.ts and the per-host\n// copy that lived in MulmoClaude's server/remoteHost/. The one change vs. those\n// copies: the path helpers take the `firestore` instance as a parameter (rather\n// than importing a module-level singleton) so a single extracted module serves\n// every host's own Firebase init. The hostId is host-specific (\"mulmoclaude\",\n// \"mulmoterminal\") and is supplied by each host — there is no discovery.\nimport { CollectionReference, DocumentData, DocumentReference, Firestore, collection, doc } from \"firebase/firestore\";\n\n// JSON payloads carried by the command channel. Explicit JSON types keep the\n// channel typed without resorting to any/unknown.\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\nexport type JsonObject = Record<string, JsonValue>;\n\n/** Structural JSON view of `T`, recursively.\n *\n * TypeScript gives an implicit index signature to type aliases and mapped\n * types but NOT to interfaces, so a payload assembled from domain interfaces\n * (`Shortcut`, `FeedSummary`, …) cannot satisfy `Record<string, JsonValue>`\n * structurally — even though it is plain JSON at runtime. Mapping over `T`\n * reconstructs it as an anonymous type, which does get that index signature.\n *\n * Recursive on purpose: a top-level-only map would still leave nested\n * interfaces (`{ shortcuts: Shortcut[] }`) unassignable, which is the case\n * every handler here actually has. */\n// The function branch must come BEFORE the object branch: a function IS an\n// object to TypeScript, so without it a function maps to `{}` and sails\n// through — the helper would accept a payload that serialises to nothing.\n// Verified: `toJsonObject({ callback: () => undefined })` compiled clean until\n// this branch existed (CodeRabbit, #2596).\nexport type Jsonify<T> = T extends JsonValue\n ? T\n : T extends (...args: never[]) => unknown\n ? never\n : T extends (infer U)[]\n ? Jsonify<U>[]\n : T extends object\n ? { [K in keyof T]: Jsonify<T[K]> }\n : never;\n\n/** Widen a JSON-shaped handler payload to the channel's `JsonObject`.\n *\n * Exists so the `Jsonify` reasoning above lives in ONE place. Before this,\n * eight remote-host handlers each carried their own `as unknown as JsonObject`\n * with the justification re-argued in eight slightly different comments —\n * which is how a rule stops being reviewable. */\nexport const toJsonObject = <T extends object>(payload: Jsonify<T>): JsonObject => payload as JsonObject;\n\n// A channel routes commands to one specific host. Both sides agree on a\n// hardcoded hostId per use case (e.g. \"mulmoclaude\", \"mulmoterminal\"); there is\n// no discovery — the remote and host just share the id.\nexport interface Channel {\n uid: string;\n hostId: string;\n}\n\nexport type CommandStatus = \"queued\" | \"processing\" | \"done\" | \"error\";\n\nexport interface CommandError {\n code: string;\n message: string;\n}\n\n// One document in a channel's commands subcollection is one API-call-like\n// request. The remote (mobile) writes method/params; the host writes\n// result/error/status.\nexport interface Command {\n method: string;\n params: JsonObject;\n status: CommandStatus;\n result: JsonValue;\n error: CommandError | null;\n createdBy: \"remote\" | \"host\";\n // Offline-queue fields (all optional; absent ⇒ pre-offline-queue behaviour, so\n // this is backward-compatible with every deployed client). Epoch-millisecond\n // NUMBERS set by the remote at enqueue time — deliberately plain numbers, not\n // Firestore Timestamps, so `isExpired` / `byCreatedAt` stay pure + browser-safe\n // and unit-testable without a Firestore fake. Clock skew over a multi-day expiry\n // window is immaterial. See plans/done/feat-remote-offline-queue.md.\n createdAt?: number; // enqueue time — age/display + best-effort dispatch bias (NOT a strict order guarantee; chat is async)\n expiresAt?: number; // deadline; past it the host deletes the command + its staged attachments\n queuedOffline?: boolean; // emitted while the host was offline (gates the remote's attachment rollback)\n}\n\n// A command is expired once `now` reaches its remote-set deadline. Absent\n// `expiresAt` ⇒ it never expires (pre-offline-queue commands). Pure with an\n// injected `now` for deterministic tests; the runner passes `Date.now()`.\nexport const isExpired = (command: Pick<Command, \"expiresAt\">, now: number): boolean => typeof command.expiresAt === \"number\" && now >= command.expiresAt;\n\n// Best-effort dispatch bias for a drained batch: oldest enqueue first. This is\n// NOT an ordering guarantee — commands run concurrently and may complete out of\n// order (chat is asynchronous, by design); it only nudges which one starts first.\n// A command with no `createdAt` sorts as oldest (0) so it is never starved.\nexport const byCreatedAt = (left: Pick<Command, \"createdAt\">, right: Pick<Command, \"createdAt\">): number => (left.createdAt ?? 0) - (right.createdAt ?? 0);\n\nexport type CommandHandler = (params: JsonObject) => JsonValue | Promise<JsonValue>;\nexport type CommandHandlers = Record<string, CommandHandler>;\n\n// Bumped when the command-channel wire protocol changes in a way the remote must\n// gate on. Advertised in the presence doc so the remote can check compatibility\n// before issuing commands.\n//\n// v2: offline queueing. The host honours `expiresAt` (deletes an expired command\n// + its staged attachments instead of spawning a stale chat). A remote MUST see\n// protocolVersion >= 2 before queueing a startChat while the host is offline —\n// a v1 host silently ignores `expiresAt`, so a queued chat would spawn stale on\n// reconnect with its uploads never cleaned up.\nexport const REMOTE_HOST_PROTOCOL_VERSION = 2;\n\n// The presence doc's payload: online flag + a capability advertisement. Written\n// by the host on every heartbeat; the remote reads it from the presence listener\n// it already runs (no extra round trip, known the instant the host is online).\n// Browser-safe so the mobile client compiles against the same shape.\n// `updatedAt` (a Firestore serverTimestamp) is added by the runner at write time\n// and is intentionally not part of this capability contract.\nexport interface HostPresence {\n online: boolean;\n hostId: string;\n protocolVersion: number;\n // Method names the host serves — the keys of the live handler table.\n capabilities: string[];\n}\n\n// Build the presence payload from the live handler table. Capabilities are\n// `Object.keys(handlers)` so registering a handler is the ONLY step needed to\n// advertise it — there is no second list to keep in sync.\nexport const buildHostPresence = (channel: Channel, handlers: CommandHandlers, online: boolean): HostPresence => ({\n online,\n hostId: channel.hostId,\n protocolVersion: REMOTE_HOST_PROTOCOL_VERSION,\n capabilities: Object.keys(handlers),\n});\n\n// Per-host command queue: users/{uid}/hosts/{hostId}/commands.\nexport const commandsCollection = (firestore: Firestore, channel: Channel): CollectionReference<DocumentData> =>\n collection(firestore, \"users\", channel.uid, \"hosts\", channel.hostId, \"commands\");\n\n// Presence doc for a host: users/{uid}/hosts/{hostId}. The host heartbeats\n// { online, updatedAt } here; the remote reads it to know if the host is up.\nexport const hostDoc = (firestore: Firestore, channel: Channel): DocumentReference<DocumentData> =>\n doc(firestore, \"users\", channel.uid, \"hosts\", channel.hostId);\n\n// Channel health as the resilient runner reports it. Browser-safe alongside the\n// wire types because the control that renders it runs in the client.\nexport { RUNNER_HEALTH_STATES, isRunnerHealth, isRunnerHealthState } from \"./health.js\";\nexport type { RunnerHealth, RunnerHealthState } from \"./health.js\";\n"],"mappings":";;AAYA,IAAa,uBAAuB;CAAC;CAAU;CAAgB;AAAS;AAWxE,IAAM,YAAY,UAAqD,OAAO,UAAU,YAAY,UAAU;AAE9G,IAAa,uBAAuB,UAA+C,qBAAqB,MAAM,UAAU,UAAU,KAAK;;;AAIvI,IAAa,kBAAkB,UAC7B,SAAS,KAAK,KACd,oBAAoB,MAAM,KAAK,MAC9B,MAAM,cAAc,QAAQ,OAAO,MAAM,cAAc,aACxD,OAAO,MAAM,cAAc;;;;;;;;;ACqB7B,IAAa,gBAAkC,YAAoC;AAyCnF,IAAa,aAAa,SAAqC,QAAyB,OAAO,QAAQ,cAAc,YAAY,OAAO,QAAQ;AAMhJ,IAAa,eAAe,MAAkC,WAA+C,KAAK,aAAa,MAAM,MAAM,aAAa;AAcxJ,IAAa,+BAA+B;AAmB5C,IAAa,qBAAqB,SAAkB,UAA2B,YAAmC;CAChH;CACA,QAAQ,QAAQ;CAChB,iBAAA;CACA,cAAc,OAAO,KAAK,QAAQ;AACpC;AAGA,IAAa,sBAAsB,WAAsB,aAAA,GAAA,mBAAA,WAAA,CAC5C,WAAW,SAAS,QAAQ,KAAK,SAAS,QAAQ,QAAQ,UAAU;AAIjF,IAAa,WAAW,WAAsB,aAAA,GAAA,mBAAA,IAAA,CACxC,WAAW,SAAS,QAAQ,KAAK,SAAS,QAAQ,MAAM"}
@@ -4,7 +4,7 @@ const require_atomic = require("./atomic-C_7YpMiM.cjs");
4
4
  const require_relPath = require("./relPath-CgGi4-Nv.cjs");
5
5
  const require_calendarGrid = require("./calendarGrid-CaS9er8i.cjs");
6
6
  const require_promptSafety = require("./promptSafety-FD8cQn3e.cjs");
7
- const require_discovery = require("./discovery-DYcR83qx.cjs");
7
+ const require_discovery = require("./discovery-NRy3tyUA.cjs");
8
8
  const require_feeds_paths = require("./feeds/paths.cjs");
9
9
  const require_skill_bridge_index = require("./skill-bridge/index.cjs");
10
10
  let node_path = require("node:path");
@@ -2133,4 +2133,4 @@ Object.defineProperty(exports, "validateRecordObject", {
2133
2133
  }
2134
2134
  });
2135
2135
 
2136
- //# sourceMappingURL=server-l4TZ5Lbf.cjs.map
2136
+ //# sourceMappingURL=server-7U-3DE2e.cjs.map