@mindstudio-ai/agent 0.1.36 → 0.1.37
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/cli.js +204 -115
- package/dist/index.d.ts +44 -18
- package/dist/index.js +194 -107
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +204 -115
- package/llms.txt +4 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,18 @@ var MindStudioError = class extends Error {
|
|
|
7
7
|
this.details = details;
|
|
8
8
|
}
|
|
9
9
|
name = "MindStudioError";
|
|
10
|
+
toString() {
|
|
11
|
+
return `MindStudioError [${this.code}] (${this.status}): ${this.message}`;
|
|
12
|
+
}
|
|
13
|
+
toJSON() {
|
|
14
|
+
return {
|
|
15
|
+
name: this.name,
|
|
16
|
+
message: this.message,
|
|
17
|
+
code: this.code,
|
|
18
|
+
status: this.status,
|
|
19
|
+
...this.details != null && { details: this.details }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
10
22
|
};
|
|
11
23
|
|
|
12
24
|
// src/http.ts
|
|
@@ -52,7 +64,10 @@ async function requestWithRetry(config, method, url, body, attempt) {
|
|
|
52
64
|
}
|
|
53
65
|
if (body2.code) code = body2.code;
|
|
54
66
|
} catch {
|
|
55
|
-
if (text
|
|
67
|
+
if (text) {
|
|
68
|
+
const stripped = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
69
|
+
if (stripped) message = stripped.slice(0, 200);
|
|
70
|
+
}
|
|
56
71
|
}
|
|
57
72
|
} catch {
|
|
58
73
|
}
|
|
@@ -85,7 +100,7 @@ var RateLimiter = class {
|
|
|
85
100
|
async acquire() {
|
|
86
101
|
if (this.callCount >= this.callCap) {
|
|
87
102
|
throw new MindStudioError(
|
|
88
|
-
`Call cap
|
|
103
|
+
`Call cap exceeded (${this.callCap} calls per execution). Reduce the number of API calls or use executeStepBatch() to combine multiple steps.`,
|
|
89
104
|
"call_cap_exceeded",
|
|
90
105
|
429
|
|
91
106
|
);
|
|
@@ -152,7 +167,7 @@ function loadConfig() {
|
|
|
152
167
|
|
|
153
168
|
// src/auth/index.ts
|
|
154
169
|
var AuthContext = class {
|
|
155
|
-
/** The current user's ID. */
|
|
170
|
+
/** The current user's ID, or null for unauthenticated users. */
|
|
156
171
|
userId;
|
|
157
172
|
/** The current user's roles in this app. */
|
|
158
173
|
roles;
|
|
@@ -191,9 +206,16 @@ var AuthContext = class {
|
|
|
191
206
|
* ```
|
|
192
207
|
*/
|
|
193
208
|
requireRole(...roles) {
|
|
209
|
+
if (this.userId == null) {
|
|
210
|
+
throw new MindStudioError(
|
|
211
|
+
"No authenticated user",
|
|
212
|
+
"unauthenticated",
|
|
213
|
+
401
|
|
214
|
+
);
|
|
215
|
+
}
|
|
194
216
|
if (!this.hasRole(...roles)) {
|
|
195
217
|
throw new MindStudioError(
|
|
196
|
-
`User
|
|
218
|
+
`User has role(s) [${this.roles.join(", ") || "none"}] but requires one of: [${roles.join(", ")}]`,
|
|
197
219
|
"forbidden",
|
|
198
220
|
403
|
|
199
221
|
);
|
|
@@ -277,11 +299,6 @@ function buildSelect(table, options = {}) {
|
|
|
277
299
|
if (options.offset != null) sql += ` OFFSET ${options.offset}`;
|
|
278
300
|
return { sql, params: params.length > 0 ? params : void 0 };
|
|
279
301
|
}
|
|
280
|
-
function buildCount(table, where, whereParams) {
|
|
281
|
-
let sql = `SELECT COUNT(*) as count FROM ${table}`;
|
|
282
|
-
if (where) sql += ` WHERE ${where}`;
|
|
283
|
-
return { sql, params: whereParams?.length ? whereParams : void 0 };
|
|
284
|
-
}
|
|
285
302
|
function buildExists(table, where, whereParams, negate) {
|
|
286
303
|
const inner = where ? `SELECT 1 FROM ${table} WHERE ${where}` : `SELECT 1 FROM ${table}`;
|
|
287
304
|
const fn = negate ? "NOT EXISTS" : "EXISTS";
|
|
@@ -352,20 +369,20 @@ function compilePredicate(fn) {
|
|
|
352
369
|
try {
|
|
353
370
|
const source = fn.toString();
|
|
354
371
|
const paramName = extractParamName(source);
|
|
355
|
-
if (!paramName) return { type: "js", fn };
|
|
372
|
+
if (!paramName) return { type: "js", fn, reason: "could not extract parameter name" };
|
|
356
373
|
const body = extractBody(source);
|
|
357
|
-
if (!body) return { type: "js", fn };
|
|
374
|
+
if (!body) return { type: "js", fn, reason: "could not extract function body" };
|
|
358
375
|
const tokens = tokenize(body);
|
|
359
|
-
if (tokens.length === 0) return { type: "js", fn };
|
|
376
|
+
if (tokens.length === 0) return { type: "js", fn, reason: "empty token stream" };
|
|
360
377
|
const parser = new Parser(tokens, paramName, fn);
|
|
361
378
|
const ast = parser.parseExpression();
|
|
362
|
-
if (!ast) return { type: "js", fn };
|
|
363
|
-
if (parser.pos < tokens.length) return { type: "js", fn };
|
|
379
|
+
if (!ast) return { type: "js", fn, reason: "could not parse expression" };
|
|
380
|
+
if (parser.pos < tokens.length) return { type: "js", fn, reason: "unexpected tokens after expression" };
|
|
364
381
|
const where = compileNode(ast);
|
|
365
|
-
if (!where) return { type: "js", fn };
|
|
382
|
+
if (!where) return { type: "js", fn, reason: "could not compile to SQL" };
|
|
366
383
|
return { type: "sql", where };
|
|
367
|
-
} catch {
|
|
368
|
-
return { type: "js", fn };
|
|
384
|
+
} catch (err) {
|
|
385
|
+
return { type: "js", fn, reason: `compilation error: ${err?.message || "unknown"}` };
|
|
369
386
|
}
|
|
370
387
|
}
|
|
371
388
|
function extractParamName(source) {
|
|
@@ -842,6 +859,9 @@ var Query = class _Query {
|
|
|
842
859
|
_limit;
|
|
843
860
|
_offset;
|
|
844
861
|
_config;
|
|
862
|
+
/** @internal Pre-compiled WHERE clause (bypasses predicate compiler). Used by Table.get(). */
|
|
863
|
+
_rawWhere;
|
|
864
|
+
_rawWhereParams;
|
|
845
865
|
/** @internal Post-process transform applied after row deserialization. */
|
|
846
866
|
_postProcess;
|
|
847
867
|
constructor(config, options) {
|
|
@@ -852,6 +872,8 @@ var Query = class _Query {
|
|
|
852
872
|
this._limit = options?.limit;
|
|
853
873
|
this._offset = options?.offset;
|
|
854
874
|
this._postProcess = options?.postProcess;
|
|
875
|
+
this._rawWhere = options?.rawWhere;
|
|
876
|
+
this._rawWhereParams = options?.rawWhereParams;
|
|
855
877
|
}
|
|
856
878
|
_clone(overrides) {
|
|
857
879
|
return new _Query(this._config, {
|
|
@@ -860,7 +882,9 @@ var Query = class _Query {
|
|
|
860
882
|
reversed: overrides.reversed ?? this._reversed,
|
|
861
883
|
limit: overrides.limit ?? this._limit,
|
|
862
884
|
offset: overrides.offset ?? this._offset,
|
|
863
|
-
postProcess: overrides.postProcess
|
|
885
|
+
postProcess: overrides.postProcess,
|
|
886
|
+
rawWhere: this._rawWhere,
|
|
887
|
+
rawWhereParams: this._rawWhereParams
|
|
864
888
|
});
|
|
865
889
|
}
|
|
866
890
|
// -------------------------------------------------------------------------
|
|
@@ -897,33 +921,16 @@ var Query = class _Query {
|
|
|
897
921
|
postProcess: (rows) => rows[0] ?? null
|
|
898
922
|
});
|
|
899
923
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
this._config.tableName,
|
|
905
|
-
compiled.sqlWhere || void 0
|
|
906
|
-
);
|
|
907
|
-
const results = await this._config.executeBatch([query]);
|
|
908
|
-
const row = results[0]?.rows[0];
|
|
909
|
-
return row?.count ?? 0;
|
|
910
|
-
}
|
|
911
|
-
const rows = await this._fetchAndFilterInJs(compiled);
|
|
912
|
-
return rows.length;
|
|
924
|
+
count() {
|
|
925
|
+
return this._clone({
|
|
926
|
+
postProcess: (rows) => rows.length
|
|
927
|
+
});
|
|
913
928
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
compiled.sqlWhere || void 0
|
|
920
|
-
);
|
|
921
|
-
const results = await this._config.executeBatch([query]);
|
|
922
|
-
const row = results[0]?.rows[0];
|
|
923
|
-
return row?.result === 1;
|
|
924
|
-
}
|
|
925
|
-
const rows = await this._fetchAndFilterInJs(compiled);
|
|
926
|
-
return rows.length > 0;
|
|
929
|
+
some() {
|
|
930
|
+
return this._clone({
|
|
931
|
+
limit: 1,
|
|
932
|
+
postProcess: (rows) => rows.length > 0
|
|
933
|
+
});
|
|
927
934
|
}
|
|
928
935
|
async every() {
|
|
929
936
|
const compiled = this._compilePredicates();
|
|
@@ -950,19 +957,19 @@ var Query = class _Query {
|
|
|
950
957
|
max(accessor) {
|
|
951
958
|
return this.sortBy(accessor).reverse().first();
|
|
952
959
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
960
|
+
groupBy(accessor) {
|
|
961
|
+
return this._clone({
|
|
962
|
+
postProcess: (rows) => {
|
|
963
|
+
const map = /* @__PURE__ */ new Map();
|
|
964
|
+
for (const row of rows) {
|
|
965
|
+
const key = accessor(row);
|
|
966
|
+
const group = map.get(key);
|
|
967
|
+
if (group) group.push(row);
|
|
968
|
+
else map.set(key, [row]);
|
|
969
|
+
}
|
|
970
|
+
return map;
|
|
963
971
|
}
|
|
964
|
-
}
|
|
965
|
-
return map;
|
|
972
|
+
});
|
|
966
973
|
}
|
|
967
974
|
// -------------------------------------------------------------------------
|
|
968
975
|
// Batch compilation — used by db.batch() to extract SQL without executing
|
|
@@ -976,6 +983,16 @@ var Query = class _Query {
|
|
|
976
983
|
* all rows and this query can filter them in JS post-fetch.
|
|
977
984
|
*/
|
|
978
985
|
_compile() {
|
|
986
|
+
if (this._rawWhere) {
|
|
987
|
+
const query = buildSelect(this._config.tableName, {
|
|
988
|
+
where: this._rawWhere,
|
|
989
|
+
whereParams: this._rawWhereParams,
|
|
990
|
+
orderBy: void 0,
|
|
991
|
+
limit: this._limit,
|
|
992
|
+
offset: this._offset
|
|
993
|
+
});
|
|
994
|
+
return { type: "query", query, fallbackQuery: null, config: this._config, postProcess: this._postProcess };
|
|
995
|
+
}
|
|
979
996
|
const compiled = this._compilePredicates();
|
|
980
997
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
981
998
|
if (compiled.allSql) {
|
|
@@ -1054,6 +1071,21 @@ var Query = class _Query {
|
|
|
1054
1071
|
// Execution internals
|
|
1055
1072
|
// -------------------------------------------------------------------------
|
|
1056
1073
|
async _execute() {
|
|
1074
|
+
if (this._rawWhere) {
|
|
1075
|
+
const query = buildSelect(this._config.tableName, {
|
|
1076
|
+
where: this._rawWhere,
|
|
1077
|
+
whereParams: this._rawWhereParams,
|
|
1078
|
+
limit: this._limit,
|
|
1079
|
+
offset: this._offset
|
|
1080
|
+
});
|
|
1081
|
+
const results = await this._config.executeBatch([query]);
|
|
1082
|
+
return results[0].rows.map(
|
|
1083
|
+
(row) => deserializeRow(
|
|
1084
|
+
row,
|
|
1085
|
+
this._config.columns
|
|
1086
|
+
)
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1057
1089
|
const compiled = this._compilePredicates();
|
|
1058
1090
|
if (compiled.allSql) {
|
|
1059
1091
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
@@ -1104,9 +1136,12 @@ var Query = class _Query {
|
|
|
1104
1136
|
}
|
|
1105
1137
|
async _fetchAndFilterInJs(compiled) {
|
|
1106
1138
|
const allRows = await this._fetchAllRows();
|
|
1107
|
-
|
|
1139
|
+
const jsFallbacks = compiled.compiled.filter((c) => c.type === "js");
|
|
1140
|
+
if (jsFallbacks.length > 0) {
|
|
1141
|
+
const reasons = jsFallbacks.map((c) => c.type === "js" ? c.reason : void 0).filter(Boolean);
|
|
1142
|
+
const reasonSuffix = reasons.length > 0 ? ` (${reasons.join("; ")})` : "";
|
|
1108
1143
|
console.warn(
|
|
1109
|
-
`[mindstudio] Filter on ${this._config.tableName} could not be compiled to SQL \u2014 scanning ${allRows.length} rows in JS`
|
|
1144
|
+
`[mindstudio] Filter on '${this._config.tableName}' could not be compiled to SQL${reasonSuffix} \u2014 scanning ${allRows.length} rows in JS`
|
|
1110
1145
|
);
|
|
1111
1146
|
}
|
|
1112
1147
|
return allRows.filter(
|
|
@@ -1179,8 +1214,10 @@ var Mutation = class _Mutation {
|
|
|
1179
1214
|
*/
|
|
1180
1215
|
_compile() {
|
|
1181
1216
|
if (this._executor) {
|
|
1182
|
-
throw new
|
|
1183
|
-
"This operation cannot be batched (e.g. removeAll with a predicate
|
|
1217
|
+
throw new MindStudioError(
|
|
1218
|
+
"This operation cannot be batched (e.g. removeAll with a JS-fallback predicate). Await it separately instead of passing to db.batch().",
|
|
1219
|
+
"not_batchable",
|
|
1220
|
+
400
|
|
1184
1221
|
);
|
|
1185
1222
|
}
|
|
1186
1223
|
return {
|
|
@@ -1217,58 +1254,61 @@ var Table = class {
|
|
|
1217
1254
|
this._config = config;
|
|
1218
1255
|
}
|
|
1219
1256
|
// -------------------------------------------------------------------------
|
|
1220
|
-
// Reads —
|
|
1257
|
+
// Reads — all return batchable Query objects (lazy until awaited)
|
|
1221
1258
|
// -------------------------------------------------------------------------
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1259
|
+
/** Get a single row by ID. Returns null if not found. */
|
|
1260
|
+
get(id) {
|
|
1261
|
+
return new Query(this._config, {
|
|
1262
|
+
rawWhere: "id = ?",
|
|
1263
|
+
rawWhereParams: [id],
|
|
1264
|
+
limit: 1,
|
|
1265
|
+
postProcess: (rows) => rows[0] ?? null
|
|
1227
1266
|
});
|
|
1228
|
-
const results = await this._config.executeBatch([query]);
|
|
1229
|
-
if (results[0].rows.length === 0) return null;
|
|
1230
|
-
return deserializeRow(
|
|
1231
|
-
results[0].rows[0],
|
|
1232
|
-
this._config.columns
|
|
1233
|
-
);
|
|
1234
1267
|
}
|
|
1235
|
-
|
|
1268
|
+
/** Find the first row matching a predicate. Returns null if none match. */
|
|
1269
|
+
findOne(predicate) {
|
|
1236
1270
|
return this.filter(predicate).first();
|
|
1237
1271
|
}
|
|
1238
|
-
|
|
1272
|
+
count(predicate) {
|
|
1239
1273
|
if (predicate) return this.filter(predicate).count();
|
|
1240
|
-
|
|
1241
|
-
const results = await this._config.executeBatch([query]);
|
|
1242
|
-
const row = results[0]?.rows[0];
|
|
1243
|
-
return row?.count ?? 0;
|
|
1274
|
+
return this.toArray().count();
|
|
1244
1275
|
}
|
|
1245
|
-
|
|
1276
|
+
/** True if any row matches the predicate. */
|
|
1277
|
+
some(predicate) {
|
|
1246
1278
|
return this.filter(predicate).some();
|
|
1247
1279
|
}
|
|
1280
|
+
/** True if all rows match the predicate. */
|
|
1248
1281
|
async every(predicate) {
|
|
1249
1282
|
return this.filter(predicate).every();
|
|
1250
1283
|
}
|
|
1284
|
+
/** True if the table has zero rows. */
|
|
1251
1285
|
async isEmpty() {
|
|
1252
1286
|
const query = buildExists(this._config.tableName, void 0, void 0, true);
|
|
1253
1287
|
const results = await this._config.executeBatch([query]);
|
|
1254
1288
|
const row = results[0]?.rows[0];
|
|
1255
1289
|
return row?.result === 1;
|
|
1256
1290
|
}
|
|
1257
|
-
|
|
1291
|
+
/** Row with the minimum value for a field, or null if table is empty. */
|
|
1292
|
+
min(accessor) {
|
|
1258
1293
|
return this.sortBy(accessor).first();
|
|
1259
1294
|
}
|
|
1260
|
-
|
|
1295
|
+
/** Row with the maximum value for a field, or null if table is empty. */
|
|
1296
|
+
max(accessor) {
|
|
1261
1297
|
return this.sortBy(accessor).reverse().first();
|
|
1262
1298
|
}
|
|
1263
|
-
|
|
1299
|
+
/** Group rows by a field. Returns a Map. */
|
|
1300
|
+
groupBy(accessor) {
|
|
1264
1301
|
return new Query(this._config).groupBy(accessor);
|
|
1265
1302
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1303
|
+
/** Get all rows as an array. */
|
|
1304
|
+
toArray() {
|
|
1305
|
+
return new Query(this._config);
|
|
1306
|
+
}
|
|
1307
|
+
/** Filter rows by a predicate. Returns a chainable Query. */
|
|
1269
1308
|
filter(predicate) {
|
|
1270
1309
|
return new Query(this._config).filter(predicate);
|
|
1271
1310
|
}
|
|
1311
|
+
/** Sort rows by a field. Returns a chainable Query. */
|
|
1272
1312
|
sortBy(accessor) {
|
|
1273
1313
|
return new Query(this._config).sortBy(accessor);
|
|
1274
1314
|
}
|
|
@@ -1295,7 +1335,11 @@ var Table = class {
|
|
|
1295
1335
|
this._config.columns
|
|
1296
1336
|
);
|
|
1297
1337
|
}
|
|
1298
|
-
|
|
1338
|
+
throw new MindStudioError(
|
|
1339
|
+
`Insert into '${this._config.tableName}' succeeded but returned no row. This may indicate a constraint violation.`,
|
|
1340
|
+
"insert_failed",
|
|
1341
|
+
500
|
|
1342
|
+
);
|
|
1299
1343
|
});
|
|
1300
1344
|
const result = isArray ? rows : rows[0];
|
|
1301
1345
|
this._syncRolesIfNeeded(
|
|
@@ -1319,6 +1363,13 @@ var Table = class {
|
|
|
1319
1363
|
this._config.columns
|
|
1320
1364
|
);
|
|
1321
1365
|
return new Mutation(this._config, [query], (results) => {
|
|
1366
|
+
if (!results[0]?.rows[0]) {
|
|
1367
|
+
throw new MindStudioError(
|
|
1368
|
+
`Row not found: no row with ID '${id}' in table '${this._config.tableName}'`,
|
|
1369
|
+
"row_not_found",
|
|
1370
|
+
404
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1322
1373
|
const result = deserializeRow(
|
|
1323
1374
|
results[0].rows[0],
|
|
1324
1375
|
this._config.columns
|
|
@@ -1333,7 +1384,9 @@ var Table = class {
|
|
|
1333
1384
|
}
|
|
1334
1385
|
remove(id) {
|
|
1335
1386
|
const query = buildDelete(this._config.tableName, `id = ?`, [id]);
|
|
1336
|
-
return new Mutation(this._config, [query], () =>
|
|
1387
|
+
return new Mutation(this._config, [query], (results) => ({
|
|
1388
|
+
deleted: results[0].changes > 0
|
|
1389
|
+
}));
|
|
1337
1390
|
}
|
|
1338
1391
|
/**
|
|
1339
1392
|
* Remove all rows matching a predicate. Returns the count removed.
|
|
@@ -1367,7 +1420,7 @@ var Table = class {
|
|
|
1367
1420
|
}
|
|
1368
1421
|
clear() {
|
|
1369
1422
|
const query = buildDelete(this._config.tableName);
|
|
1370
|
-
return new Mutation(this._config, [query], () =>
|
|
1423
|
+
return new Mutation(this._config, [query], (results) => results[0].changes);
|
|
1371
1424
|
}
|
|
1372
1425
|
/**
|
|
1373
1426
|
* Insert a row, or update it if a row with the same unique key already
|
|
@@ -1385,6 +1438,15 @@ var Table = class {
|
|
|
1385
1438
|
this._validateUniqueConstraint(conflictColumns);
|
|
1386
1439
|
const withDefaults = this._config.defaults ? { ...this._config.defaults, ...data } : data;
|
|
1387
1440
|
this._checkManagedColumns(withDefaults);
|
|
1441
|
+
for (const col of conflictColumns) {
|
|
1442
|
+
if (!(col in withDefaults)) {
|
|
1443
|
+
throw new MindStudioError(
|
|
1444
|
+
`Upsert on ${this._config.tableName} requires "${col}" in data (conflict key)`,
|
|
1445
|
+
"missing_conflict_key",
|
|
1446
|
+
400
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1388
1450
|
const query = buildUpsert(
|
|
1389
1451
|
this._config.tableName,
|
|
1390
1452
|
withDefaults,
|
|
@@ -1392,6 +1454,13 @@ var Table = class {
|
|
|
1392
1454
|
this._config.columns
|
|
1393
1455
|
);
|
|
1394
1456
|
return new Mutation(this._config, [query], (results) => {
|
|
1457
|
+
if (!results[0]?.rows[0]) {
|
|
1458
|
+
throw new MindStudioError(
|
|
1459
|
+
`Upsert into ${this._config.tableName} returned no row`,
|
|
1460
|
+
"upsert_failed",
|
|
1461
|
+
500
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1395
1464
|
const result = deserializeRow(
|
|
1396
1465
|
results[0].rows[0],
|
|
1397
1466
|
this._config.columns
|
|
@@ -1543,7 +1612,7 @@ function createDb(databases, executeBatch, authConfig, syncRoles) {
|
|
|
1543
1612
|
if (c.type === "query") {
|
|
1544
1613
|
if (!c.query && c.predicates?.length) {
|
|
1545
1614
|
console.warn(
|
|
1546
|
-
`[mindstudio] db.batch(): filter on ${c.config.tableName} could not be compiled to SQL \u2014 processing in JS`
|
|
1615
|
+
`[mindstudio] db.batch(): filter on '${c.config.tableName}' could not be compiled to SQL \u2014 processing in JS`
|
|
1547
1616
|
);
|
|
1548
1617
|
}
|
|
1549
1618
|
return Query._processResults(results[0], c);
|
|
@@ -3018,9 +3087,14 @@ var stepMetadata = {
|
|
|
3018
3087
|
},
|
|
3019
3088
|
"sendEmail": {
|
|
3020
3089
|
stepType: "sendEmail",
|
|
3021
|
-
description: "Send an email to one or more
|
|
3022
|
-
usageNotes:
|
|
3023
|
-
|
|
3090
|
+
description: "Send an email to one or more recipient addresses.",
|
|
3091
|
+
usageNotes: `- Use the "to" field to send to specific email addresses directly. For v2 apps, recipients must be verified users in the app's user table.
|
|
3092
|
+
- Alternatively, recipient email addresses can be resolved from OAuth connections configured by the app creator via connectionId. The user running the workflow does not specify the recipient directly.
|
|
3093
|
+
- If the body is a URL to a hosted HTML file on the CDN, the HTML is fetched and used as the email body.
|
|
3094
|
+
- When generateHtml is enabled, the body text is converted to a styled HTML email using an AI model.
|
|
3095
|
+
- connectionId can be a comma-separated list to send to multiple recipients.
|
|
3096
|
+
- The special connectionId "trigger_email" uses the email address that triggered the workflow.`,
|
|
3097
|
+
inputSchema: { "type": "object", "properties": { "subject": { "type": "string", "description": "Email subject line" }, "body": { "type": "string", "description": "Email body content (plain text, markdown, HTML, or a CDN URL to an HTML file)" }, "to": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "connectionId": { "type": "string", "description": "OAuth connection ID(s) for the recipient(s), comma-separated for multiple" }, "generateHtml": { "type": "boolean", "description": "When true, auto-convert the body text into a styled HTML email using AI" }, "generateHtmlInstructions": { "type": "string", "description": "Natural language instructions for the HTML generation style" }, "generateHtmlModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "properties": {}, "required": [], "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model settings override for HTML generation" }, "attachments": { "type": "array", "items": { "type": "string" }, "description": "URLs of files to attach to the email" } }, "required": ["subject", "body"] },
|
|
3024
3098
|
outputSchema: { "type": "object", "properties": { "recipients": { "type": "array", "items": { "type": "string" }, "description": "Email addresses the message was sent to" } }, "required": ["recipients"] }
|
|
3025
3099
|
},
|
|
3026
3100
|
"sendGmailDraft": {
|
|
@@ -3342,7 +3416,7 @@ var MindStudioAgent = class {
|
|
|
3342
3416
|
const res = await fetch(data.outputUrl);
|
|
3343
3417
|
if (!res.ok) {
|
|
3344
3418
|
throw new MindStudioError(
|
|
3345
|
-
`Failed to fetch output from S3: ${res.status} ${res.statusText}`,
|
|
3419
|
+
`Failed to fetch ${stepType} output from S3: ${res.status} ${res.statusText}`,
|
|
3346
3420
|
"output_fetch_error",
|
|
3347
3421
|
res.status
|
|
3348
3422
|
);
|
|
@@ -3405,13 +3479,27 @@ var MindStudioAgent = class {
|
|
|
3405
3479
|
this._httpConfig.rateLimiter.updateFromHeaders(res.headers);
|
|
3406
3480
|
if (!res.ok) {
|
|
3407
3481
|
this._httpConfig.rateLimiter.release();
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
res.
|
|
3413
|
-
|
|
3414
|
-
|
|
3482
|
+
let message = `${res.status} ${res.statusText}`;
|
|
3483
|
+
let code = "api_error";
|
|
3484
|
+
let details;
|
|
3485
|
+
try {
|
|
3486
|
+
const text = await res.text();
|
|
3487
|
+
try {
|
|
3488
|
+
const body2 = JSON.parse(text);
|
|
3489
|
+
details = body2;
|
|
3490
|
+
const errMsg = (typeof body2.error === "string" ? body2.error : void 0) ?? (typeof body2.message === "string" ? body2.message : void 0) ?? (typeof body2.details === "string" ? body2.details : void 0);
|
|
3491
|
+
if (errMsg) message = errMsg;
|
|
3492
|
+
else if (body2.error || body2.message || body2.details) {
|
|
3493
|
+
message = JSON.stringify(body2.error ?? body2.message ?? body2.details);
|
|
3494
|
+
}
|
|
3495
|
+
if (body2.code) code = body2.code;
|
|
3496
|
+
} catch {
|
|
3497
|
+
const stripped = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
3498
|
+
if (stripped) message = stripped.slice(0, 200);
|
|
3499
|
+
}
|
|
3500
|
+
} catch {
|
|
3501
|
+
}
|
|
3502
|
+
throw new MindStudioError(`[${stepType}] ${message}`, code, res.status, details);
|
|
3415
3503
|
}
|
|
3416
3504
|
const headers = res.headers;
|
|
3417
3505
|
try {
|
|
@@ -3444,7 +3532,7 @@ var MindStudioAgent = class {
|
|
|
3444
3532
|
};
|
|
3445
3533
|
} else if (event.type === "error") {
|
|
3446
3534
|
throw new MindStudioError(
|
|
3447
|
-
event.error || "Step execution failed"
|
|
3535
|
+
`[${stepType}] ${event.error || "Step execution failed"}`,
|
|
3448
3536
|
"step_error",
|
|
3449
3537
|
500
|
|
3450
3538
|
);
|
|
@@ -3483,7 +3571,7 @@ var MindStudioAgent = class {
|
|
|
3483
3571
|
}
|
|
3484
3572
|
if (!doneEvent) {
|
|
3485
3573
|
throw new MindStudioError(
|
|
3486
|
-
|
|
3574
|
+
`[${stepType}] Stream ended unexpectedly without completing. The step execution may have been interrupted.`,
|
|
3487
3575
|
"stream_error",
|
|
3488
3576
|
500
|
|
3489
3577
|
);
|
|
@@ -3495,7 +3583,7 @@ var MindStudioAgent = class {
|
|
|
3495
3583
|
const s3Res = await fetch(doneEvent.outputUrl);
|
|
3496
3584
|
if (!s3Res.ok) {
|
|
3497
3585
|
throw new MindStudioError(
|
|
3498
|
-
`Failed to fetch output from S3: ${s3Res.status} ${s3Res.statusText}`,
|
|
3586
|
+
`Failed to fetch ${stepType} output from S3: ${s3Res.status} ${s3Res.statusText}`,
|
|
3499
3587
|
"output_fetch_error",
|
|
3500
3588
|
s3Res.status
|
|
3501
3589
|
);
|
|
@@ -3856,7 +3944,7 @@ var MindStudioAgent = class {
|
|
|
3856
3944
|
}
|
|
3857
3945
|
if (!this._auth) {
|
|
3858
3946
|
throw new MindStudioError(
|
|
3859
|
-
"Auth context not
|
|
3947
|
+
"Auth context not loaded. Call `await agent.ensureContext()` first, or perform any db operation (which auto-loads context).",
|
|
3860
3948
|
"context_not_loaded",
|
|
3861
3949
|
400
|
|
3862
3950
|
);
|
|
@@ -4030,13 +4118,12 @@ var MindStudioAgent = class {
|
|
|
4030
4118
|
if (!res.ok) {
|
|
4031
4119
|
const text = await res.text().catch(() => "");
|
|
4032
4120
|
console.warn(
|
|
4033
|
-
`[mindstudio]
|
|
4121
|
+
`[mindstudio] Role sync failed for user ${userId} (${res.status}${text ? ": " + text.slice(0, 100) : ""}). Roles were saved to the database but may not be reflected in auth.hasRole() until the next successful write.`
|
|
4034
4122
|
);
|
|
4035
4123
|
}
|
|
4036
4124
|
} catch (err) {
|
|
4037
4125
|
console.warn(
|
|
4038
|
-
`[mindstudio]
|
|
4039
|
-
err
|
|
4126
|
+
`[mindstudio] Role sync failed for user ${userId}: network error. Roles were saved to the database but may not be reflected in auth.hasRole() until the next successful write.`
|
|
4040
4127
|
);
|
|
4041
4128
|
}
|
|
4042
4129
|
}
|