@mindstudio-ai/agent 0.1.107 → 0.1.108

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.
package/dist/index.js CHANGED
@@ -102,9 +102,12 @@ function runWithContext(ctx, fn) {
102
102
  }
103
103
 
104
104
  // src/db-ws.ts
105
- var WS_REQUEST_TIMEOUT_MS = 3e5;
105
+ var DB_WS_SUBPROTOCOL = "ms-db-v2";
106
+ var WS_ACK_TIMEOUT_MS = 2e4;
107
+ var WS_IDLE_TIMEOUT_MS = 3e5;
106
108
  var WS_OPEN_TIMEOUT_MS = 5e3;
107
109
  var WS_OPEN = 1;
110
+ var WS_CHUNK_CHARS = 256 * 1024;
108
111
  var WS_MAX_FRAME_CHARS = 9e5;
109
112
  var DbWsTransportError = class extends Error {
110
113
  constructor(message, sent = false) {
@@ -117,13 +120,86 @@ function isReadOnlySql(sql) {
117
120
  const head = sql.trimStart().slice(0, 8).toUpperCase();
118
121
  return head.startsWith("SELECT") || head.startsWith("PRAGMA") || head.startsWith("EXPLAIN");
119
122
  }
120
- var socket = null;
123
+ var conn = null;
121
124
  var opening = null;
122
125
  var idCounter = 0;
123
- var pending = /* @__PURE__ */ new Map();
124
- function getOrOpen(url) {
125
- if (socket && socket.readyState === WS_OPEN) {
126
- return Promise.resolve(socket);
126
+ function clearPendingTimer(p) {
127
+ if (p.timer !== null) {
128
+ clearTimeout(p.timer);
129
+ p.timer = null;
130
+ }
131
+ }
132
+ function forget(self, id) {
133
+ self.pending.delete(id);
134
+ self.inbound.delete(id);
135
+ if (self.retired && self.pending.size === 0) {
136
+ try {
137
+ self.ws.close();
138
+ } catch {
139
+ }
140
+ }
141
+ }
142
+ function retire(self, reason) {
143
+ if (self.retired) {
144
+ return;
145
+ }
146
+ self.retired = true;
147
+ if (conn === self) {
148
+ conn = null;
149
+ }
150
+ try {
151
+ console.warn(
152
+ `[mindstudio] db: retiring WebSocket after ${reason}; next query reconnects.`
153
+ );
154
+ } catch {
155
+ }
156
+ if (self.pending.size === 0) {
157
+ try {
158
+ self.ws.close();
159
+ } catch {
160
+ }
161
+ }
162
+ }
163
+ function armIdleTimer(self, id, p) {
164
+ clearPendingTimer(p);
165
+ p.timer = setTimeout(() => {
166
+ forget(self, id);
167
+ retire(self, "a reply went silent");
168
+ p.reject(new DbWsTransportError("ws reply went silent", true));
169
+ }, WS_IDLE_TIMEOUT_MS);
170
+ }
171
+ function deliver(self, msg) {
172
+ const p = self.pending.get(msg.id);
173
+ if (!p) {
174
+ return;
175
+ }
176
+ clearPendingTimer(p);
177
+ forget(self, msg.id);
178
+ if (msg.error) {
179
+ p.reject(
180
+ new MindStudioError(
181
+ `[db] ${msg.error.message || "Database query failed"}`,
182
+ msg.error.code || "db_query_error",
183
+ 400,
184
+ msg.error
185
+ )
186
+ );
187
+ } else {
188
+ p.resolve(msg.results);
189
+ }
190
+ }
191
+ function failTransport(self, id, err) {
192
+ const p = self.pending.get(id);
193
+ if (!p) {
194
+ return;
195
+ }
196
+ clearPendingTimer(p);
197
+ forget(self, id);
198
+ p.reject(err);
199
+ }
200
+ function getOrOpen(url, token) {
201
+ if (conn && !conn.retired && conn.ws.readyState === WS_OPEN) {
202
+ return Promise.resolve(conn);
127
203
  }
128
204
  if (opening) {
129
205
  return opening;
@@ -136,17 +212,22 @@ function getOrOpen(url) {
136
212
  }
137
213
  opening = new Promise((resolve, reject) => {
138
214
  let ws;
215
+ let self = null;
139
216
  const fail = (err) => {
140
217
  clearTimeout(openTimer);
141
- if (socket === ws) {
142
- socket = null;
218
+ if (self) {
219
+ self.retired = true;
220
+ if (conn === self) {
221
+ conn = null;
222
+ }
223
+ for (const [, p] of self.pending) {
224
+ clearPendingTimer(p);
225
+ p.reject(new DbWsTransportError(err.message, true));
226
+ }
227
+ self.pending.clear();
228
+ self.inbound.clear();
143
229
  }
144
230
  opening = null;
145
- for (const [, p] of pending) {
146
- clearTimeout(p.timer);
147
- p.reject(new DbWsTransportError(err.message, true));
148
- }
149
- pending.clear();
150
231
  reject(err);
151
232
  };
152
233
  const openTimer = setTimeout(() => {
@@ -157,16 +238,26 @@ function getOrOpen(url) {
157
238
  fail(new DbWsTransportError("ws open timed out"));
158
239
  }, WS_OPEN_TIMEOUT_MS);
159
240
  try {
160
- ws = new WebSocketCtor(url);
241
+ ws = new WebSocketCtor(url, ["auth", token, DB_WS_SUBPROTOCOL]);
161
242
  } catch (err) {
162
243
  fail(new DbWsTransportError(err?.message || "ws construct failed"));
163
244
  return;
164
245
  }
246
+ self = {
247
+ ws,
248
+ pending: /* @__PURE__ */ new Map(),
249
+ chunked: false,
250
+ inbound: /* @__PURE__ */ new Map(),
251
+ acksSeen: false,
252
+ retired: false
253
+ };
254
+ const c = self;
165
255
  ws.onopen = () => {
166
256
  clearTimeout(openTimer);
167
- socket = ws;
257
+ c.chunked = ws.protocol === DB_WS_SUBPROTOCOL;
258
+ conn = c;
168
259
  opening = null;
169
- resolve(ws);
260
+ resolve(c);
170
261
  };
171
262
  ws.onmessage = (ev) => {
172
263
  let msg;
@@ -177,26 +268,54 @@ function getOrOpen(url) {
177
268
  } catch {
178
269
  return;
179
270
  }
180
- if (!msg || msg.t !== "db-result" || msg.id == null) {
271
+ if (!msg || msg.id == null) {
181
272
  return;
182
273
  }
183
- const p = pending.get(msg.id);
184
- if (!p) {
274
+ if (msg.t === "db-ack") {
275
+ c.acksSeen = true;
276
+ const p = c.pending.get(msg.id);
277
+ if (p && !p.acked) {
278
+ p.acked = true;
279
+ armIdleTimer(c, msg.id, p);
280
+ }
185
281
  return;
186
282
  }
187
- clearTimeout(p.timer);
188
- pending.delete(msg.id);
189
- if (msg.error) {
190
- p.reject(
191
- new MindStudioError(
192
- `[db] ${msg.error.message || "Database query failed"}`,
193
- msg.error.code || "db_query_error",
194
- 400,
195
- msg.error
196
- )
197
- );
198
- } else {
199
- p.resolve(msg.results);
283
+ if (msg.t === "db-chunk") {
284
+ const p = c.pending.get(msg.id);
285
+ if (!p || typeof msg.data !== "string") {
286
+ return;
287
+ }
288
+ const parts = c.inbound.get(msg.id);
289
+ if (parts) {
290
+ parts.push(msg.data);
291
+ } else {
292
+ c.inbound.set(msg.id, [msg.data]);
293
+ }
294
+ armIdleTimer(c, msg.id, p);
295
+ return;
296
+ }
297
+ if (msg.t === "db-end") {
298
+ const parts = c.inbound.get(msg.id);
299
+ if (!parts) {
300
+ return;
301
+ }
302
+ c.inbound.delete(msg.id);
303
+ let assembled;
304
+ try {
305
+ assembled = JSON.parse(parts.join(""));
306
+ } catch {
307
+ failTransport(
308
+ c,
309
+ msg.id,
310
+ new DbWsTransportError("chunked reply did not parse", true)
311
+ );
312
+ return;
313
+ }
314
+ deliver(c, assembled);
315
+ return;
316
+ }
317
+ if (msg.t === "db-result") {
318
+ deliver(c, msg);
200
319
  }
201
320
  };
202
321
  ws.onerror = () => fail(new DbWsTransportError("ws error"));
@@ -204,28 +323,59 @@ function getOrOpen(url) {
204
323
  });
205
324
  return opening;
206
325
  }
326
+ function* outboundFrames(id, json) {
327
+ if (json.length <= WS_CHUNK_CHARS) {
328
+ yield json;
329
+ return;
330
+ }
331
+ let seq = 0;
332
+ for (let offset = 0; offset < json.length; offset += WS_CHUNK_CHARS) {
333
+ yield JSON.stringify({
334
+ t: "db-chunk",
335
+ id,
336
+ seq: seq++,
337
+ data: json.slice(offset, offset + WS_CHUNK_CHARS)
338
+ });
339
+ }
340
+ yield JSON.stringify({ t: "db-end", id });
341
+ }
207
342
  async function executeDbBatchOverWs(url, token, databaseId, queries) {
208
343
  const id = ++idCounter;
209
344
  const frame = JSON.stringify({ t: "db", id, token, databaseId, queries });
210
- if (frame.length > WS_MAX_FRAME_CHARS) {
345
+ const self = await getOrOpen(url, token);
346
+ if (!self.chunked && frame.length > WS_MAX_FRAME_CHARS) {
211
347
  throw new DbWsTransportError("payload too large for ws");
212
348
  }
213
- const ws = await getOrOpen(url);
214
349
  return await new Promise((resolve, reject) => {
215
- const timer = setTimeout(() => {
216
- pending.delete(id);
217
- reject(new DbWsTransportError("ws execute timed out", true));
218
- }, WS_REQUEST_TIMEOUT_MS);
219
- pending.set(id, { resolve, reject, timer });
350
+ const pending = { resolve, reject, timer: null, acked: false };
351
+ if (self.acksSeen) {
352
+ pending.timer = setTimeout(() => {
353
+ forget(self, id);
354
+ retire(self, "a request went unacknowledged");
355
+ reject(new DbWsTransportError("ws ack timed out", true));
356
+ }, WS_ACK_TIMEOUT_MS);
357
+ } else {
358
+ armIdleTimer(self, id, pending);
359
+ }
360
+ self.pending.set(id, pending);
361
+ let started = false;
362
+ let complete = false;
220
363
  try {
221
- ws.send(frame);
364
+ for (const out of outboundFrames(id, frame)) {
365
+ self.ws.send(out);
366
+ started = true;
367
+ }
368
+ complete = true;
222
369
  } catch (err) {
223
- clearTimeout(timer);
224
- pending.delete(id);
370
+ clearPendingTimer(pending);
371
+ forget(self, id);
372
+ if (started) {
373
+ retire(self, "a partial request upload failed");
374
+ }
225
375
  reject(
226
376
  new DbWsTransportError(
227
377
  `ws send failed: ${err?.message || "unknown"}`,
228
- false
378
+ complete
229
379
  )
230
380
  );
231
381
  }
@@ -2814,6 +2964,50 @@ function createAnalytics(call) {
2814
2964
  };
2815
2965
  }
2816
2966
 
2967
+ // src/email/index.ts
2968
+ var iso = (v) => v === void 0 ? void 0 : v instanceof Date ? v.toISOString() : v;
2969
+ function createEmail(call) {
2970
+ return {
2971
+ stats(options) {
2972
+ return call("stats", {
2973
+ start: iso(options?.start),
2974
+ end: iso(options?.end),
2975
+ buckets: options?.buckets
2976
+ });
2977
+ },
2978
+ messages(options) {
2979
+ return call("messages", {
2980
+ ...options,
2981
+ start: iso(options?.start),
2982
+ end: iso(options?.end)
2983
+ });
2984
+ },
2985
+ message(messageId) {
2986
+ return call("message", { messageId });
2987
+ },
2988
+ batches(options) {
2989
+ return call("batches", {
2990
+ start: iso(options?.start),
2991
+ end: iso(options?.end),
2992
+ limit: options?.limit,
2993
+ offset: options?.offset
2994
+ });
2995
+ },
2996
+ batch(batchId) {
2997
+ return call("batch", { batchId });
2998
+ },
2999
+ suppressions(options) {
3000
+ return call("suppressions", options ?? {});
3001
+ },
3002
+ suppress(email2) {
3003
+ return call("suppress", { email: email2 });
3004
+ },
3005
+ unsuppress(email2) {
3006
+ return call("unsuppress", { email: email2 });
3007
+ }
3008
+ };
3009
+ }
3010
+
2817
3011
  // src/task/index.ts
2818
3012
  import { randomUUID as randomUUID2 } from "crypto";
2819
3013
 
@@ -5526,6 +5720,7 @@ var MindStudioAgent = class {
5526
5720
  _dataSources;
5527
5721
  _voice;
5528
5722
  _analytics;
5723
+ _email;
5529
5724
  /** @internal Auth type — 'internal' for CALLBACK_TOKEN (managed mode), 'apiKey' otherwise. */
5530
5725
  _authType;
5531
5726
  /** @internal Usage source sent on step executions (from MINDSTUDIO_REQUEST_SOURCE).
@@ -6570,6 +6765,24 @@ var MindStudioAgent = class {
6570
6765
  this._analyticsRequest.bind(this)
6571
6766
  );
6572
6767
  }
6768
+ /**
6769
+ * Outbound email: the per-recipient delivery log, blast stats, and this app's
6770
+ * unsubscribe list. Sending is the `sendEmail` action, not this namespace.
6771
+ *
6772
+ * @example
6773
+ * ```ts
6774
+ * import { email } from '@mindstudio-ai/agent';
6775
+ *
6776
+ * // A support lookup: did this person get their mail?
6777
+ * const { messages } = await email.messages({ recipient: 'a@b.com' });
6778
+ *
6779
+ * // How did the August newsletter do?
6780
+ * const blast = await email.batch('newsletter-2026-08');
6781
+ * ```
6782
+ */
6783
+ get email() {
6784
+ return this._email ??= createEmail(this._emailRequest.bind(this));
6785
+ }
6573
6786
  /**
6574
6787
  * Jewel surfaces: arrival-shaped triggers (`propose`) and the app-native
6575
6788
  * approval queue (`queue.list` / `queue.resolve`). See {@link JewelsApi}.
@@ -6708,6 +6921,18 @@ var MindStudioAgent = class {
6708
6921
  * of surfacing as empty data. Deliberately single-shot and scoped to this
6709
6922
  * namespace — the other brokered transports keep their semantics.
6710
6923
  */
6924
+ /**
6925
+ * Brokered email transport. No 429 retry, unlike analytics: this namespace
6926
+ * carries writes (suppress / unsuppress), and silently retrying a mutation is a
6927
+ * different risk from retrying an idempotent read. A caller that wants one can
6928
+ * retry a read itself.
6929
+ */
6930
+ async _emailRequest(op, body) {
6931
+ return this._brokeredRequest("email", op, body, {
6932
+ fallbackMessage: "Email API call failed",
6933
+ fallbackCode: "email_error"
6934
+ });
6935
+ }
6711
6936
  async _analyticsRequest(op, body) {
6712
6937
  const call = () => this._brokeredRequest("analytics", op, body, {
6713
6938
  fallbackMessage: "Analytics read failed",
@@ -7612,6 +7837,13 @@ var analytics = new Proxy({}, {
7612
7837
  return typeof value === "function" ? value.bind(target) : value;
7613
7838
  }
7614
7839
  });
7840
+ var email = new Proxy({}, {
7841
+ get(_, prop) {
7842
+ const target = mindstudio.email;
7843
+ const value = Reflect.get(target, prop, target);
7844
+ return typeof value === "function" ? value.bind(target) : value;
7845
+ }
7846
+ });
7615
7847
  var session = new Proxy(
7616
7848
  {},
7617
7849
  {
@@ -7640,6 +7872,7 @@ export {
7640
7872
  db,
7641
7873
  index_default as default,
7642
7874
  defineJewel,
7875
+ email,
7643
7876
  files,
7644
7877
  getRequestContext,
7645
7878
  mindstudio,