@medicine-wheel/mcp 4.5.0 → 4.5.3

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.
@@ -50,6 +50,14 @@ interface StoredBeat {
50
50
  timestamp: string;
51
51
  act: number;
52
52
  relations_honored: string[];
53
+ cycle_id?: string;
54
+ parent_beat_id?: string;
55
+ sub_beats?: string[];
56
+ origin?: {
57
+ producer: string;
58
+ source_ref?: string;
59
+ method?: string;
60
+ };
53
61
  }
54
62
  interface StoredCycle {
55
63
  id: string;
@@ -175,11 +183,40 @@ interface PlanPerspectiveFilters {
175
183
  session_id?: string;
176
184
  id?: string;
177
185
  }
186
+ /** Carries the HTTP status so callers can tell "endpoint absent" from "server broke". */
187
+ export declare class HttpStoreError extends Error {
188
+ readonly status?: number;
189
+ readonly url: string;
190
+ constructor(message: string, url: string, status?: number);
191
+ }
192
+ /** A write that did not land. Kept so a failure survives past the console. */
193
+ export interface WriteFailure {
194
+ operation: string;
195
+ url: string;
196
+ message: string;
197
+ at: string;
198
+ }
178
199
  export declare class HttpStore {
179
200
  readonly name = "http";
180
201
  private readonly baseUrl;
202
+ private readonly failures;
203
+ private readonly inFlight;
181
204
  constructor(baseUrl: string);
182
- createNode(node: StoredNode): void;
205
+ /**
206
+ * Writes keep their statement-call shape — `store.createNode(node)` still
207
+ * compiles and still returns immediately — but the promise now comes back,
208
+ * so a caller that awaits learns the truth instead of being told "created"
209
+ * over a request that 404'd. The internal catch is what keeps a rejection
210
+ * nobody awaited from taking the server down as an unhandled rejection.
211
+ */
212
+ private track;
213
+ /** Writes that did not land, oldest first. Non-destructive. */
214
+ getWriteFailures(): WriteFailure[];
215
+ /** Drain the record — for a caller that has reported the failures onward. */
216
+ takeWriteFailures(): WriteFailure[];
217
+ /** Wait for every in-flight write to land or fail. Never rejects. */
218
+ settleWrites(): Promise<void>;
219
+ createNode(node: StoredNode): Promise<void>;
183
220
  getNode(id: string): Promise<StoredNode | undefined>;
184
221
  getAllNodes(limit?: number): Promise<StoredNode[]>;
185
222
  getNodesByType(type: string): Promise<StoredNode[]>;
@@ -189,35 +226,36 @@ export declare class HttpStore {
189
226
  direction?: string;
190
227
  limit?: number;
191
228
  }): Promise<StoredNode[]>;
192
- createEdge(edge: StoredEdge): void;
229
+ createEdge(edge: StoredEdge): Promise<void>;
193
230
  getAllEdges(): Promise<StoredEdge[]>;
194
231
  getEdgesForNode(nodeId: string): Promise<StoredEdge[]>;
195
232
  getRelatedNodeIds(nodeId: string): Promise<string[]>;
196
- updateEdgeCeremony(fromId: string, toId: string, ceremonyId: string): void;
233
+ updateEdgeCeremony(fromId: string, toId: string, ceremonyId: string): Promise<void>;
197
234
  getRelationalWeb(nodeId: string, depth?: number): Promise<{
198
235
  nodes: StoredNode[];
199
236
  edges: StoredEdge[];
200
237
  }>;
201
- logCeremony(ceremony: StoredCeremony): void;
238
+ logCeremony(ceremony: StoredCeremony): Promise<void>;
202
239
  getCeremony(id: string): Promise<StoredCeremony | undefined>;
203
240
  getAllCeremonies(limit?: number): Promise<StoredCeremony[]>;
204
241
  getCeremoniesByDirection(direction: string): Promise<StoredCeremony[]>;
205
242
  getCeremoniesByType(type: string): Promise<StoredCeremony[]>;
206
- createBeat(beat: StoredBeat): void;
243
+ createBeat(beat: StoredBeat): Promise<void>;
207
244
  getBeat(id: string): Promise<StoredBeat | undefined>;
208
245
  getAllBeats(limit?: number): Promise<StoredBeat[]>;
209
246
  getBeatsByDirection(direction: string): Promise<StoredBeat[]>;
210
- createCycle(cycle: StoredCycle): void;
247
+ createCycle(cycle: StoredCycle): Promise<void>;
248
+ private listCycles;
211
249
  getCycle(id: string): Promise<StoredCycle | undefined>;
212
250
  getAllCycles(): Promise<{
213
251
  active: StoredCycle[];
214
252
  archived: StoredCycle[];
215
253
  }>;
216
- archiveCycle(id: string): void;
217
- saveChart(chart: StoredChart): void;
254
+ archiveCycle(id: string): Promise<void>;
255
+ saveChart(chart: StoredChart): Promise<void>;
218
256
  getChart(id: string): Promise<StoredChart | undefined>;
219
257
  getAllCharts(direction?: string): Promise<StoredChart[]>;
220
- saveMmot(mmot: StoredMmot): void;
258
+ saveMmot(mmot: StoredMmot): Promise<void>;
221
259
  getMmotsByChart(chartId: string): Promise<StoredMmot[]>;
222
260
  registerInquiryWeave(record: StoredInquiryWeave): Promise<void>;
223
261
  getInquiryWeave(id: string): Promise<StoredInquiryWeave | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"http-store.d.ts","sourceRoot":"","sources":["../src/http-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-store.d.ts","sourceRoot":"","sources":["../src/http-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,UAAU;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,GAAG,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,SAAS,EAAE;QACT,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,OAAO,GAAG,uBAAuB,CAAC;QACtE,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,mBAAmB;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,CAAC,CAAC;IACf,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,SAAS,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,EAAE,CAAC;IACtE,MAAM,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE;YACV,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,mBAAmB,CAAC,EAAE,MAAM,CAAC;YAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;QACF,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,sBAAsB;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAID,yFAAyF;AACzF,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM1D;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAwHD,qBAAa,SAAS;IACpB,QAAQ,CAAC,IAAI,UAAU;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAEzC,OAAO,EAAE,MAAM;IAO3B;;;;;;OAMG;IACH,OAAO,CAAC,KAAK;IAoBb,+DAA+D;IAC/D,gBAAgB,IAAI,YAAY,EAAE;IAIlC,6EAA6E;IAC7E,iBAAiB,IAAI,YAAY,EAAE;IAInC,qEAAqE;IAC/D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQnC,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKpD,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAMlD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAKnD,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAK7D,WAAW,CACf,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAC/D,OAAO,CAAC,UAAU,EAAE,CAAC;IAexB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAKpC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAKtD,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAU1D,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7E,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,KAAK,SAAI,GACR,OAAO,CAAC;QAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QAAC,KAAK,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC;IA+BxD,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAK5D,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAQ3D,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAKtE,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAOlE,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKpD,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAMlD,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAOnE,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAKhC,UAAU;IAKlB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAKtD,YAAY,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QAAC,QAAQ,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC;IAQjF,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvC,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAKtD,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAgB9D,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAcvD,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/D,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAOpE,iBAAiB,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAcnF,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAQtF,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAgB1E,oBAAoB,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;CAUnG;AAID,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEvD"}
@@ -9,54 +9,183 @@
9
9
  * Default behavior (MW_DATA_DIR / local JSONL) is preserved.
10
10
  */
11
11
  // ── Helpers ──
12
+ /** Carries the HTTP status so callers can tell "endpoint absent" from "server broke". */
13
+ export class HttpStoreError extends Error {
14
+ status;
15
+ url;
16
+ constructor(message, url, status) {
17
+ super(message);
18
+ this.name = 'HttpStoreError';
19
+ this.url = url;
20
+ this.status = status;
21
+ }
22
+ }
23
+ const DEFAULT_TIMEOUT_MS = 15_000;
24
+ const MAX_TRACKED_FAILURES = 50;
25
+ function requestTimeoutMs() {
26
+ const parsed = Number(process.env.MW_HTTP_TIMEOUT_MS);
27
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
28
+ }
29
+ function errorMessage(error) {
30
+ return error instanceof Error ? error.message : String(error);
31
+ }
32
+ function snippet(text) {
33
+ const flat = text.replace(/\s+/g, ' ').trim();
34
+ return flat.length > 200 ? `${flat.slice(0, 200)}…` : flat;
35
+ }
36
+ function describeShape(value) {
37
+ if (value === null)
38
+ return 'null';
39
+ if (Array.isArray(value))
40
+ return 'an array';
41
+ return `a ${typeof value}`;
42
+ }
43
+ /**
44
+ * Every request carries a deadline. Without one, a server that accepts the
45
+ * connection and then never answers parks the MCP server's single thread of
46
+ * conversation forever — the tool call never returns and never errors.
47
+ */
48
+ async function httpFetch(url, init) {
49
+ const method = init?.method ?? 'GET';
50
+ const timeoutMs = requestTimeoutMs();
51
+ const controller = new AbortController();
52
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
53
+ try {
54
+ return await fetch(url, { ...init, signal: controller.signal });
55
+ }
56
+ catch (error) {
57
+ if (controller.signal.aborted) {
58
+ throw new HttpStoreError(`HTTP ${method} ${url} timed out after ${timeoutMs}ms`, url);
59
+ }
60
+ throw new HttpStoreError(`HTTP ${method} ${url} failed: ${errorMessage(error)}`, url);
61
+ }
62
+ finally {
63
+ clearTimeout(timer);
64
+ }
65
+ }
66
+ /**
67
+ * A reverse proxy or a misrouted path answers 200 with an HTML page. Left to
68
+ * res.json(), that surfaces as "Unexpected token <" — which names neither the
69
+ * endpoint that lied nor what it actually said.
70
+ */
71
+ async function readJson(res, url, method) {
72
+ const text = await res.text();
73
+ try {
74
+ return JSON.parse(text);
75
+ }
76
+ catch {
77
+ const contentType = res.headers.get('content-type') ?? 'no content-type';
78
+ throw new HttpStoreError(`HTTP ${method} ${url} returned non-JSON (${contentType}): ${snippet(text)}`, url, res.status);
79
+ }
80
+ }
81
+ /**
82
+ * The server has drifted between `[...]` and `{ nodes: [...] }` for the same
83
+ * collection. Accept either. A payload that is neither is a real failure and
84
+ * must not reach the caller disguised as an empty collection.
85
+ */
86
+ function readCollection(payload, key, url) {
87
+ if (Array.isArray(payload))
88
+ return payload;
89
+ if (payload !== null && typeof payload === 'object') {
90
+ const inner = payload[key];
91
+ if (Array.isArray(inner))
92
+ return inner;
93
+ }
94
+ throw new HttpStoreError(`HTTP GET ${url} returned ${describeShape(payload)} — expected an array or { ${key}: [...] }`, url);
95
+ }
96
+ /** True only when the endpoint itself is absent — never for a server that broke. */
97
+ function isEndpointMissing(error) {
98
+ return error instanceof HttpStoreError && error.status === 404;
99
+ }
12
100
  async function httpGet(url) {
13
- const res = await fetch(url);
101
+ const res = await httpFetch(url);
14
102
  if (!res.ok) {
15
- throw new Error(`HTTP GET ${url} failed: ${res.status} ${res.statusText}`);
103
+ const text = await res.text().catch(() => '');
104
+ throw new HttpStoreError(`HTTP GET ${url} failed: ${res.status} ${res.statusText} — ${snippet(text)}`, url, res.status);
16
105
  }
17
- return res.json();
106
+ return (await readJson(res, url, 'GET'));
18
107
  }
19
108
  async function httpPost(url, body) {
20
- const res = await fetch(url, {
109
+ const res = await httpFetch(url, {
21
110
  method: 'POST',
22
111
  headers: { 'Content-Type': 'application/json' },
23
112
  body: JSON.stringify(body),
24
113
  });
25
114
  if (!res.ok) {
26
115
  const text = await res.text().catch(() => '');
27
- throw new Error(`HTTP POST ${url} failed: ${res.status} ${res.statusText} — ${text}`);
116
+ throw new HttpStoreError(`HTTP POST ${url} failed: ${res.status} ${res.statusText} — ${snippet(text)}`, url, res.status);
28
117
  }
29
- return res.json();
118
+ return (await readJson(res, url, 'POST'));
30
119
  }
31
120
  // ── HttpStore ──
32
121
  export class HttpStore {
33
122
  name = 'http';
34
123
  baseUrl;
124
+ failures = [];
125
+ inFlight = new Set();
35
126
  constructor(baseUrl) {
36
127
  // Strip trailing slash for consistent URL construction
37
128
  this.baseUrl = baseUrl.replace(/\/+$/, '');
38
129
  }
130
+ // === Write accountability ===
131
+ /**
132
+ * Writes keep their statement-call shape — `store.createNode(node)` still
133
+ * compiles and still returns immediately — but the promise now comes back,
134
+ * so a caller that awaits learns the truth instead of being told "created"
135
+ * over a request that 404'd. The internal catch is what keeps a rejection
136
+ * nobody awaited from taking the server down as an unhandled rejection.
137
+ */
138
+ track(operation, url, request) {
139
+ const settled = request.then(() => undefined, (error) => {
140
+ const message = errorMessage(error);
141
+ console.error(`[http-store] ${operation} failed: ${message}`);
142
+ this.failures.push({ operation, url, message, at: new Date().toISOString() });
143
+ if (this.failures.length > MAX_TRACKED_FAILURES)
144
+ this.failures.shift();
145
+ throw error;
146
+ });
147
+ // Attaching a handler to `settled` is what marks it handled for the
148
+ // call sites that ignore the returned promise.
149
+ const quiet = settled.catch(() => undefined);
150
+ this.inFlight.add(quiet);
151
+ void quiet.then(() => { this.inFlight.delete(quiet); });
152
+ return settled;
153
+ }
154
+ /** Writes that did not land, oldest first. Non-destructive. */
155
+ getWriteFailures() {
156
+ return [...this.failures];
157
+ }
158
+ /** Drain the record — for a caller that has reported the failures onward. */
159
+ takeWriteFailures() {
160
+ return this.failures.splice(0, this.failures.length);
161
+ }
162
+ /** Wait for every in-flight write to land or fail. Never rejects. */
163
+ async settleWrites() {
164
+ while (this.inFlight.size > 0) {
165
+ await Promise.all([...this.inFlight]);
166
+ }
167
+ }
39
168
  // === Nodes ===
40
169
  createNode(node) {
41
- // Fire-and-forget; callers don't await in JsonlStore either
42
- httpPost(`${this.baseUrl}/api/nodes`, node).catch(err => console.error(`[http-store] createNode failed: ${err}`));
170
+ const url = `${this.baseUrl}/api/nodes`;
171
+ return this.track('createNode', url, httpPost(url, node));
43
172
  }
44
173
  async getNode(id) {
45
174
  const all = await this.getAllNodes();
46
175
  return all.find(n => n.id === id);
47
176
  }
48
177
  async getAllNodes(limit) {
49
- const data = await httpGet(`${this.baseUrl}/api/nodes`);
50
- const nodes = data.nodes ?? [];
178
+ const url = `${this.baseUrl}/api/nodes`;
179
+ const nodes = readCollection(await httpGet(url), 'nodes', url);
51
180
  return limit !== undefined ? nodes.slice(0, limit) : nodes;
52
181
  }
53
182
  async getNodesByType(type) {
54
- const data = await httpGet(`${this.baseUrl}/api/nodes?type=${encodeURIComponent(type)}`);
55
- return data.nodes ?? [];
183
+ const url = `${this.baseUrl}/api/nodes?type=${encodeURIComponent(type)}`;
184
+ return readCollection(await httpGet(url), 'nodes', url);
56
185
  }
57
186
  async getNodesByDirection(direction) {
58
- const data = await httpGet(`${this.baseUrl}/api/nodes?direction=${encodeURIComponent(direction)}`);
59
- return data.nodes ?? [];
187
+ const url = `${this.baseUrl}/api/nodes?direction=${encodeURIComponent(direction)}`;
188
+ return readCollection(await httpGet(url), 'nodes', url);
60
189
  }
61
190
  async searchNodes(query, opts = {}) {
62
191
  // Server has no search endpoint yet — fetch all and filter client-side
@@ -72,10 +201,12 @@ export class HttpStore {
72
201
  }
73
202
  // === Edges ===
74
203
  createEdge(edge) {
75
- httpPost(`${this.baseUrl}/api/edges`, edge).catch(err => console.error(`[http-store] createEdge failed: ${err}`));
204
+ const url = `${this.baseUrl}/api/edges`;
205
+ return this.track('createEdge', url, httpPost(url, edge));
76
206
  }
77
207
  async getAllEdges() {
78
- return httpGet(`${this.baseUrl}/api/edges`);
208
+ const url = `${this.baseUrl}/api/edges`;
209
+ return readCollection(await httpGet(url), 'edges', url);
79
210
  }
80
211
  async getEdgesForNode(nodeId) {
81
212
  const edges = await this.getAllEdges();
@@ -94,28 +225,20 @@ export class HttpStore {
94
225
  }
95
226
  updateEdgeCeremony(fromId, toId, ceremonyId) {
96
227
  // No dedicated server endpoint — fetch, update, and re-post
97
- (async () => {
98
- try {
99
- const edges = await httpGet(`${this.baseUrl}/api/edges`);
100
- for (const edge of edges) {
101
- if ((edge.from_id === fromId && edge.to_id === toId) ||
102
- (edge.from_id === toId && edge.to_id === fromId)) {
103
- await httpPost(`${this.baseUrl}/api/edges`, {
104
- ...edge,
105
- ceremony_honored: true,
106
- ceremony_id: ceremonyId,
107
- });
108
- }
228
+ const url = `${this.baseUrl}/api/edges`;
229
+ return this.track('updateEdgeCeremony', url, (async () => {
230
+ const edges = await this.getAllEdges();
231
+ for (const edge of edges) {
232
+ if ((edge.from_id === fromId && edge.to_id === toId) ||
233
+ (edge.from_id === toId && edge.to_id === fromId)) {
234
+ await httpPost(url, { ...edge, ceremony_honored: true, ceremony_id: ceremonyId });
109
235
  }
110
236
  }
111
- catch (err) {
112
- console.error(`[http-store] updateEdgeCeremony failed: ${err}`);
113
- }
114
- })();
237
+ })());
115
238
  }
116
239
  async getRelationalWeb(nodeId, depth = 2) {
117
240
  const allNodes = await this.getAllNodes();
118
- const allEdges = await httpGet(`${this.baseUrl}/api/edges`);
241
+ const allEdges = await this.getAllEdges();
119
242
  const nodeMap = new Map(allNodes.map(n => [n.id, n]));
120
243
  const visited = new Set();
121
244
  const resultNodes = [];
@@ -143,35 +266,38 @@ export class HttpStore {
143
266
  }
144
267
  // === Ceremonies ===
145
268
  logCeremony(ceremony) {
146
- httpPost(`${this.baseUrl}/api/ceremonies`, ceremony).catch(err => console.error(`[http-store] logCeremony failed: ${err}`));
269
+ const url = `${this.baseUrl}/api/ceremonies`;
270
+ return this.track('logCeremony', url, httpPost(url, ceremony));
147
271
  }
148
272
  async getCeremony(id) {
149
273
  const all = await this.getAllCeremonies();
150
274
  return all.find(c => c.id === id);
151
275
  }
152
276
  async getAllCeremonies(limit) {
153
- const data = await httpGet(`${this.baseUrl}/api/ceremonies`);
154
- const sorted = (data.ceremonies ?? []).sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
277
+ const url = `${this.baseUrl}/api/ceremonies`;
278
+ const sorted = readCollection(await httpGet(url), 'ceremonies', url).sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
155
279
  return limit !== undefined ? sorted.slice(0, limit) : sorted;
156
280
  }
157
281
  async getCeremoniesByDirection(direction) {
158
- const data = await httpGet(`${this.baseUrl}/api/ceremonies?direction=${encodeURIComponent(direction)}`);
159
- return data.ceremonies ?? [];
282
+ const url = `${this.baseUrl}/api/ceremonies?direction=${encodeURIComponent(direction)}`;
283
+ return readCollection(await httpGet(url), 'ceremonies', url);
160
284
  }
161
285
  async getCeremoniesByType(type) {
162
- const data = await httpGet(`${this.baseUrl}/api/ceremonies?type=${encodeURIComponent(type)}`);
163
- return data.ceremonies ?? [];
286
+ const url = `${this.baseUrl}/api/ceremonies?type=${encodeURIComponent(type)}`;
287
+ return readCollection(await httpGet(url), 'ceremonies', url);
164
288
  }
165
289
  // === Beats ===
166
290
  createBeat(beat) {
167
- httpPost(`${this.baseUrl}/api/narrative/beats`, beat).catch(err => console.error(`[http-store] createBeat failed: ${err}`));
291
+ const url = `${this.baseUrl}/api/narrative/beats`;
292
+ return this.track('createBeat', url, httpPost(url, beat));
168
293
  }
169
294
  async getBeat(id) {
170
295
  const all = await this.getAllBeats();
171
296
  return all.find(b => b.id === id);
172
297
  }
173
298
  async getAllBeats(limit) {
174
- const beats = await httpGet(`${this.baseUrl}/api/narrative/beats`);
299
+ const url = `${this.baseUrl}/api/narrative/beats`;
300
+ const beats = readCollection(await httpGet(url), 'beats', url);
175
301
  return limit !== undefined ? beats.slice(0, limit) : beats;
176
302
  }
177
303
  async getBeatsByDirection(direction) {
@@ -180,14 +306,19 @@ export class HttpStore {
180
306
  }
181
307
  // === Cycles ===
182
308
  createCycle(cycle) {
183
- httpPost(`${this.baseUrl}/api/narrative/cycles`, cycle).catch(err => console.error(`[http-store] createCycle failed: ${err}`));
309
+ const url = `${this.baseUrl}/api/narrative/cycles`;
310
+ return this.track('createCycle', url, httpPost(url, cycle));
311
+ }
312
+ async listCycles() {
313
+ const url = `${this.baseUrl}/api/narrative/cycles`;
314
+ return readCollection(await httpGet(url), 'cycles', url);
184
315
  }
185
316
  async getCycle(id) {
186
- const all = await httpGet(`${this.baseUrl}/api/narrative/cycles`);
317
+ const all = await this.listCycles();
187
318
  return all.find(c => c.id === id);
188
319
  }
189
320
  async getAllCycles() {
190
- const all = await httpGet(`${this.baseUrl}/api/narrative/cycles`);
321
+ const all = await this.listCycles();
191
322
  return {
192
323
  active: all.filter(c => !c.archived),
193
324
  archived: all.filter(c => c.archived),
@@ -195,62 +326,59 @@ export class HttpStore {
195
326
  }
196
327
  archiveCycle(id) {
197
328
  // No dedicated archive endpoint — fetch, mark archived, and re-post
198
- (async () => {
199
- try {
200
- const all = await httpGet(`${this.baseUrl}/api/narrative/cycles`);
201
- const cycle = all.find(c => c.id === id);
202
- if (cycle) {
203
- await httpPost(`${this.baseUrl}/api/narrative/cycles`, {
204
- ...cycle,
205
- archived: true,
206
- });
207
- }
208
- }
209
- catch (err) {
210
- console.error(`[http-store] archiveCycle failed: ${err}`);
211
- }
212
- })();
329
+ const url = `${this.baseUrl}/api/narrative/cycles`;
330
+ return this.track('archiveCycle', url, (async () => {
331
+ const all = await this.listCycles();
332
+ const cycle = all.find(c => c.id === id);
333
+ // An archive that found nothing to archive is a failed archive. Reported
334
+ // as success it tells the circle a cycle was closed that is still open.
335
+ if (!cycle)
336
+ throw new HttpStoreError(`archiveCycle: cycle ${id} not found`, url, 404);
337
+ await httpPost(url, { ...cycle, archived: true });
338
+ })());
213
339
  }
214
340
  // === Charts ===
215
- // No server endpoint exists yet — these use a fallback pattern
216
- // that logs a warning and returns empty results.
341
+ // A deployment may predate /api/charts. That absence (404) is a fact worth
342
+ // degrading over; a 500, a timeout or an HTML page is not, and returning []
343
+ // for those tells the caller "no charts exist" when the truth is unknown.
217
344
  saveChart(chart) {
218
- httpPost(`${this.baseUrl}/api/charts`, chart).catch(err => console.error(`[http-store] saveChart: /api/charts not available — ${err}`));
345
+ const url = `${this.baseUrl}/api/charts`;
346
+ return this.track('saveChart', url, httpPost(url, chart));
219
347
  }
220
348
  async getChart(id) {
221
- try {
222
- const all = await this.getAllCharts();
223
- return all.find(c => c.id === id);
224
- }
225
- catch {
226
- console.error(`[http-store] getChart: /api/charts not available`);
227
- return undefined;
228
- }
349
+ const all = await this.getAllCharts();
350
+ return all.find(c => c.id === id);
229
351
  }
230
352
  async getAllCharts(direction) {
353
+ const url = `${this.baseUrl}/api/charts`;
354
+ let charts;
231
355
  try {
232
- const charts = await httpGet(`${this.baseUrl}/api/charts`);
233
- let result = Array.isArray(charts) ? charts : [];
234
- if (direction)
235
- result = result.filter(c => c.direction === direction);
236
- return result.sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
356
+ charts = readCollection(await httpGet(url), 'charts', url);
237
357
  }
238
- catch {
239
- console.error(`[http-store] getAllCharts: /api/charts not available`);
358
+ catch (error) {
359
+ if (!isEndpointMissing(error))
360
+ throw error;
361
+ console.error(`[http-store] getAllCharts: ${url} not available`);
240
362
  return [];
241
363
  }
364
+ const result = direction ? charts.filter(c => c.direction === direction) : [...charts];
365
+ return result.sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
242
366
  }
243
367
  // === MMOT ===
244
368
  saveMmot(mmot) {
245
- httpPost(`${this.baseUrl}/api/mmots`, mmot).catch(err => console.error(`[http-store] saveMmot: /api/mmots not available — ${err}`));
369
+ const url = `${this.baseUrl}/api/mmots`;
370
+ return this.track('saveMmot', url, httpPost(url, mmot));
246
371
  }
247
372
  async getMmotsByChart(chartId) {
373
+ const url = `${this.baseUrl}/api/mmots`;
248
374
  try {
249
- const all = await httpGet(`${this.baseUrl}/api/mmots`);
250
- return (Array.isArray(all) ? all : []).filter(m => m.chart_id === chartId);
375
+ const all = readCollection(await httpGet(url), 'mmots', url);
376
+ return all.filter(m => m.chart_id === chartId);
251
377
  }
252
- catch {
253
- console.error(`[http-store] getMmotsByChart: /api/mmots not available`);
378
+ catch (error) {
379
+ if (!isEndpointMissing(error))
380
+ throw error;
381
+ console.error(`[http-store] getMmotsByChart: ${url} not available`);
254
382
  return [];
255
383
  }
256
384
  }
@@ -273,8 +401,8 @@ export class HttpStore {
273
401
  if (filters.artefact !== undefined)
274
402
  params.set('artefact', filters.artefact);
275
403
  const query = params.size > 0 ? `?${params.toString()}` : '';
276
- const data = await httpGet(`${this.baseUrl}/api/inquiry-weaves${query}`);
277
- return data.inquiry_weaves ?? [];
404
+ const url = `${this.baseUrl}/api/inquiry-weaves${query}`;
405
+ return readCollection(await httpGet(url), 'inquiry_weaves', url);
278
406
  }
279
407
  // === Plan Perspectives ===
280
408
  async registerPlanPerspective(record) {
@@ -282,13 +410,15 @@ export class HttpStore {
282
410
  return data.record ?? record;
283
411
  }
284
412
  async getPlanPerspective(id) {
285
- const res = await fetch(`${this.baseUrl}/api/plan-perspectives/${encodeURIComponent(id)}`);
413
+ const url = `${this.baseUrl}/api/plan-perspectives/${encodeURIComponent(id)}`;
414
+ const res = await httpFetch(url);
286
415
  if (res.status === 404)
287
416
  return undefined;
288
417
  if (!res.ok) {
289
- throw new Error(`HTTP GET /api/plan-perspectives/${id} failed: ${res.status} ${res.statusText}`);
418
+ const text = await res.text().catch(() => '');
419
+ throw new HttpStoreError(`HTTP GET ${url} failed: ${res.status} ${res.statusText} — ${snippet(text)}`, url, res.status);
290
420
  }
291
- const data = (await res.json());
421
+ const data = (await readJson(res, url, 'GET'));
292
422
  return data.record ?? undefined;
293
423
  }
294
424
  async listPlanPerspectives(filters = {}) {
@@ -300,8 +430,8 @@ export class HttpStore {
300
430
  if (filters.id !== undefined)
301
431
  params.set('id', filters.id);
302
432
  const query = params.size > 0 ? `?${params.toString()}` : '';
303
- const data = await httpGet(`${this.baseUrl}/api/plan-perspectives${query}`);
304
- return data.plan_perspectives ?? [];
433
+ const url = `${this.baseUrl}/api/plan-perspectives${query}`;
434
+ return readCollection(await httpGet(url), 'plan_perspectives', url);
305
435
  }
306
436
  }
307
437
  // ── Factory ──