@mindstudio-ai/remy 0.1.156 → 0.1.158

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/headless.js CHANGED
@@ -5570,7 +5570,8 @@ async function runTurn(params) {
5570
5570
  return;
5571
5571
  }
5572
5572
  const contentBlocks = [];
5573
- let thinkingStartedAt = 0;
5573
+ const thinkingBlockStartTimes = [];
5574
+ let thinkingCompleteCount = 0;
5574
5575
  const toolInputAccumulators = /* @__PURE__ */ new Map();
5575
5576
  let stopReason = "end_turn";
5576
5577
  let subAgentText = "";
@@ -5694,8 +5695,8 @@ async function runTurn(params) {
5694
5695
  break;
5695
5696
  }
5696
5697
  case "thinking":
5697
- if (!thinkingStartedAt) {
5698
- thinkingStartedAt = event.ts;
5698
+ if (event.text === "") {
5699
+ thinkingBlockStartTimes.push(event.ts);
5699
5700
  }
5700
5701
  onEvent({ type: "thinking", text: event.text });
5701
5702
  break;
@@ -5704,10 +5705,10 @@ async function runTurn(params) {
5704
5705
  type: "thinking",
5705
5706
  thinking: event.thinking,
5706
5707
  signature: event.signature,
5707
- startedAt: thinkingStartedAt,
5708
+ startedAt: thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts,
5708
5709
  completedAt: event.ts
5709
5710
  });
5710
- thinkingStartedAt = 0;
5711
+ thinkingCompleteCount++;
5711
5712
  break;
5712
5713
  case "tool_input_delta": {
5713
5714
  const acc = getOrCreateAccumulator2(event.id, event.name);
package/dist/index.js CHANGED
@@ -6153,7 +6153,8 @@ async function runTurn(params) {
6153
6153
  return;
6154
6154
  }
6155
6155
  const contentBlocks = [];
6156
- let thinkingStartedAt = 0;
6156
+ const thinkingBlockStartTimes = [];
6157
+ let thinkingCompleteCount = 0;
6157
6158
  const toolInputAccumulators = /* @__PURE__ */ new Map();
6158
6159
  let stopReason = "end_turn";
6159
6160
  let subAgentText = "";
@@ -6277,8 +6278,8 @@ async function runTurn(params) {
6277
6278
  break;
6278
6279
  }
6279
6280
  case "thinking":
6280
- if (!thinkingStartedAt) {
6281
- thinkingStartedAt = event.ts;
6281
+ if (event.text === "") {
6282
+ thinkingBlockStartTimes.push(event.ts);
6282
6283
  }
6283
6284
  onEvent({ type: "thinking", text: event.text });
6284
6285
  break;
@@ -6287,10 +6288,10 @@ async function runTurn(params) {
6287
6288
  type: "thinking",
6288
6289
  thinking: event.thinking,
6289
6290
  signature: event.signature,
6290
- startedAt: thinkingStartedAt,
6291
+ startedAt: thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts,
6291
6292
  completedAt: event.ts
6292
6293
  });
6293
- thinkingStartedAt = 0;
6294
+ thinkingCompleteCount++;
6294
6295
  break;
6295
6296
  case "tool_input_delta": {
6296
6297
  const acc = getOrCreateAccumulator2(event.id, event.name);
@@ -145,18 +145,26 @@ export async function listVendors(input: {
145
145
  status?: string;
146
146
  search?: string;
147
147
  }) {
148
- const vendors = await Vendors
149
- .filter(v => {
150
- if (input.status && v.status !== input.status) return false;
151
- if (input.search && !v.name.includes(input.search)) return false;
152
- return true;
153
- })
154
- .sortBy(v => v.name);
155
-
148
+ let q = Vendors.sortBy(v => v.name);
149
+ if (input.status) {
150
+ q = q.filter(
151
+ (v, $) => v.status === $.status,
152
+ { status: input.status }, // bindings: lifts closure var so filter compiles to SQL
153
+ );
154
+ }
155
+ if (input.search) {
156
+ q = q.filter(
157
+ (v, $) => v.name.includes($.search),
158
+ { search: input.search }, // bindings (LIKE): lifts closure var so filter compiles to SQL
159
+ );
160
+ }
161
+ const vendors = await q;
156
162
  return { vendors };
157
163
  }
158
164
  ```
159
165
 
166
+ Building the query progressively (one `.filter` per optional input) is the canonical shape — each clause compiles to SQL independently and the bindings form keeps `input.*` references out of closures.
167
+
160
168
  ### Role-Gated Operation
161
169
 
162
170
  ```typescript
@@ -195,25 +195,89 @@ const cleared = await Vendors.clear(); // number (
195
195
 
196
196
  ### Filter Predicates
197
197
 
198
- Predicates are arrow functions compiled to SQL WHERE clauses:
198
+ Predicates are arrow functions compiled to SQL WHERE clauses. Two forms — closure (literal-only) and bindings (when the predicate references an outer-scope value):
199
199
 
200
200
  ```typescript
201
- Vendors.filter(v => v.status === 'approved') // equality
202
- Vendors.filter(v => v.totalCents > 10000) // comparison
203
- Vendors.filter(v => v.totalCents >= 5000 && v.totalCents <= 50000) // range
204
- Vendors.filter(v => v.paymentTerms !== null) // null check
205
- Vendors.filter(v => v.status === 'approved' && v.totalCents > 10000) // logical AND
206
- Vendors.filter(v => v.status === 'approved' || v.status === 'pending') // logical OR
207
- Vendors.filter(v => ['approved', 'pending'].includes(v.status)) // IN
208
- Vendors.filter(v => v.name.includes('Acme')) // LIKE
209
- Vendors.filter(v => v.address.city === 'New York') // nested JSON
210
- const minAmount = 10000;
211
- Vendors.filter(v => v.totalCents > minAmount) // captured variables
201
+ // Closure form works for literals only:
202
+ Vendors.filter(v => v.status === 'approved');
203
+
204
+ // Bindings form required when the predicate references an outer-scope
205
+ // value (input.*, auth.*, foreign keys collected earlier, etc.). Without
206
+ // bindings, the predicate falls back to fetching every row and filtering
207
+ // in JS — a warning is logged when this happens.
208
+ Vendors.filter(
209
+ (v, $) => v.companyId === $.companyId,
210
+ { companyId: input.companyId }, // bindings: lifts closure var so filter compiles to SQL
211
+ );
212
212
  ```
213
213
 
214
- **What compiles to SQL** (efficient): equality, comparisons, `&&`/`||`, `.includes()` for arrays and strings, null checks, boolean negation, captured variables.
214
+ Both produce identical results. The bindings form is faster on tables of any size and dramatically faster on large tables.
215
+
216
+ #### Patterns
217
+
218
+ ```typescript
219
+ // Equality / inequality
220
+ Companies.filter((c, $) => c.ownerId === $.ownerId, { ownerId: auth.userId }); // bindings: lifts closure var so filter compiles to SQL
221
+
222
+ // Comparison / range
223
+ Investments.filter((i, $) => i.amountInvested >= $.minAmount, { minAmount: 10000 }); // bindings: lifts closure var so filter compiles to SQL
224
+
225
+ // Mixed bindings + literal — freely combinable
226
+ Investments.filter(
227
+ (i, $) => i.companyId === $.companyId && i.status === 'active',
228
+ { companyId: input.companyId }, // bindings: lifts closure var so filter compiles to SQL
229
+ );
230
+
231
+ // IN clause — array binding
232
+ ContactRelationships.filter(
233
+ (r, $) => $.contactIds.includes(r.contactId),
234
+ { contactIds: ['a', 'b', 'c'] }, // bindings: lifts closure var so filter compiles to SQL
235
+ );
236
+
237
+ // LIKE clause — string binding
238
+ SimpleRecords.filter(
239
+ (r, $) => r.slug.includes($.prefix),
240
+ { prefix: 'lat-' }, // bindings: lifts closure var so filter compiles to SQL
241
+ );
242
+
243
+ // Nested keys
244
+ Orders.filter(
245
+ (o, $) => o.companyId === $.user.companyId,
246
+ { user: { companyId: input.companyId } }, // bindings: lifts closure var so filter compiles to SQL
247
+ );
248
+
249
+ // removeAll with bindings — single DELETE WHERE … instead of fetch-all-then-delete-by-id
250
+ SimpleRecords.removeAll(
251
+ (r, $) => r.slug.includes($.prefix),
252
+ { prefix: 'lat-' }, // bindings: lifts closure var so removeAll compiles to SQL
253
+ );
254
+ ```
255
+
256
+ #### What compiles to SQL
257
+
258
+ - Literals: equality, comparisons, range, null checks, `&&`/`||`/`!`, `.includes()` for arrays and strings, nested JSON access (`v.address.city === 'New York'`).
259
+ - Outer-scope values when passed via bindings (same shapes as above).
260
+
261
+ #### What falls back to JS
262
+
263
+ - Closure references without a bindings argument: `o => o.x === input.x`.
264
+ - Two-param predicate without a bindings argument: `(o, $) => …` but no `{…}` second arg.
265
+ - Mixed predicate where some closure refs aren't lifted (e.g. `(o, $) => o.companyId === $.companyId && o.status === stale` where `stale` is from outer scope). Falls back for the *whole* predicate — lift everything outer-scope into bindings.
266
+ - Bindings keys missing or `undefined` (predicate references `$.foo` but bindings has no `foo`). The SDK does not silently substitute NULL.
267
+ - Bindings value is a non-scalar/non-array (e.g. `$.user` is `{...}`). Read scalar leaves only.
268
+ - `.startsWith()`, regex, computed expressions like `o.a + o.b > 100`.
269
+
270
+ A warning is logged whenever a predicate falls back to JS. Avoid these patterns on large tables.
271
+
272
+ #### When to use bindings — and when to skip
273
+
274
+ Use bindings whenever a predicate compares against `input.*`, `auth.*`, request params, foreign keys collected earlier, or any other function-scope value, and the table can grow beyond a few hundred rows. Always use them inside `db.batch(...)` where table size could grow over time.
275
+
276
+ Skip bindings when the predicate only references the row plus string/number literals — those already compile to SQL with no help. Also fine to skip on small fixed-size tables (under ~100 rows) where the JS fallback is fast enough not to matter.
277
+
278
+ #### Inline-comment requirement
215
279
 
216
- **What falls back to JS** (fetches all rows, filters in memory): `.startsWith()`, regex, computed expressions like `o.a + o.b > 100`, complex closures. A warning is logged when this happens. Avoid these patterns on large tables.
280
+ When you write a bindings argument, include a brief inline comment on the bindings object explaining its purpose. Without the comment, downstream coding agents read the bindings object as boilerplate and "simplify" it back to closure form — silently regressing performance. The canonical phrasing is `// bindings: lifts closure var so filter compiles to SQL`. Exact wording isn't critical; what matters is naming the failure mode.
217
281
 
218
282
  ### Time Helpers
219
283
 
@@ -251,11 +315,15 @@ Write methods throw `MindStudioError` with specific codes:
251
315
  `db.batch()` combines multiple operations into a single HTTP round-trip. Every `await` on a table operation is a network call, so batching is critical for performance. Use it whenever you have multiple reads, writes, or a mix of both. `upsert()` works in batches just like `push()` and `update()`:
252
316
 
253
317
  ```typescript
254
- // Reads: fetch related data in one call instead of sequential awaits
318
+ // Reads: fetch related data in one call instead of sequential awaits.
319
+ // Note the bindings on PurchaseOrders — without it, that filter would
320
+ // fall back to JS because vendorId is a closure capture. Hoisting a
321
+ // shared `$` lets every filter in the batch reuse it.
322
+ const $ = { vendorId }; // bindings: lifts closure var so filters compile to SQL
255
323
  const [vendors, orders, invoiceCount] = await db.batch(
256
- Vendors.filter(v => v.status === 'approved'),
257
- PurchaseOrders.filter(po => po.vendorId === vendorId),
258
- Invoices.count(i => i.status === 'pending'),
324
+ Vendors.filter(v => v.status === 'approved'), // literal — no bindings needed
325
+ PurchaseOrders.filter((po, $) => po.vendorId === $.vendorId, $), // bindings
326
+ Invoices.count(i => i.status === 'pending'), // literal — no bindings needed
259
327
  );
260
328
 
261
329
  // Writes: batch multiple updates instead of awaiting each one in a loop
@@ -35,6 +35,8 @@ For multi-step tasks with branching logic (research, enrichment, content pipelin
35
35
 
36
36
  For methods that take more than a few seconds, use `stream()` from `@mindstudio-ai/agent` to push real-time progress to the frontend. Pipe `onLog` from SDK actions through `stream()` so users see what's happening. The frontend calls the method with `stream: true` and gets updates via `onToken`. See the methods reference for the full pattern.
37
37
 
38
+ When writing `db` filter predicates that reference outer-scope values (`input.*`, `auth.*`, foreign keys collected earlier, etc.), use the bindings form so the filter compiles to SQL — see `tables.md` "Filter Predicates" for patterns and the inline-comment convention.
39
+
38
40
  ### Auth
39
41
  - Not every app needs auth, and even for apps that do need auth, not every screen needs auth. Think intentionally about places where auth is required. Don't make auth be the first thing a user sees - that's jarring. Only show auth at intuitive and natural moments in the user's journey - be thoughtful about how to implement auth in the UI.
40
42
  - Frontend interfaces are always untrusted. Always enforce auth in backend methods. Use frontend auth and role information as a hint to conditionally show/hide UI to make the experience pleasant and seamless for users depending on their state, but remember to always use backend methods for gating data that is conditional on auth.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.156",
3
+ "version": "0.1.158",
4
4
  "description": "MindStudio coding agent",
5
5
  "repository": {
6
6
  "type": "git",