@mindstudio-ai/agent 0.1.36 → 0.1.38
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 +212 -115
- package/dist/index.d.ts +44 -18
- package/dist/index.js +199 -107
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +212 -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
|
);
|
|
@@ -259,6 +281,11 @@ function deserializeRow(row, columns) {
|
|
|
259
281
|
} catch {
|
|
260
282
|
result[key] = value;
|
|
261
283
|
}
|
|
284
|
+
} else if (col?.type === "boolean" && typeof value === "number") {
|
|
285
|
+
result[key] = value !== 0;
|
|
286
|
+
} else if (col?.type === "number" && typeof value === "string") {
|
|
287
|
+
const num = Number(value);
|
|
288
|
+
result[key] = Number.isNaN(num) ? value : num;
|
|
262
289
|
} else {
|
|
263
290
|
result[key] = value;
|
|
264
291
|
}
|
|
@@ -277,11 +304,6 @@ function buildSelect(table, options = {}) {
|
|
|
277
304
|
if (options.offset != null) sql += ` OFFSET ${options.offset}`;
|
|
278
305
|
return { sql, params: params.length > 0 ? params : void 0 };
|
|
279
306
|
}
|
|
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
307
|
function buildExists(table, where, whereParams, negate) {
|
|
286
308
|
const inner = where ? `SELECT 1 FROM ${table} WHERE ${where}` : `SELECT 1 FROM ${table}`;
|
|
287
309
|
const fn = negate ? "NOT EXISTS" : "EXISTS";
|
|
@@ -352,20 +374,20 @@ function compilePredicate(fn) {
|
|
|
352
374
|
try {
|
|
353
375
|
const source = fn.toString();
|
|
354
376
|
const paramName = extractParamName(source);
|
|
355
|
-
if (!paramName) return { type: "js", fn };
|
|
377
|
+
if (!paramName) return { type: "js", fn, reason: "could not extract parameter name" };
|
|
356
378
|
const body = extractBody(source);
|
|
357
|
-
if (!body) return { type: "js", fn };
|
|
379
|
+
if (!body) return { type: "js", fn, reason: "could not extract function body" };
|
|
358
380
|
const tokens = tokenize(body);
|
|
359
|
-
if (tokens.length === 0) return { type: "js", fn };
|
|
381
|
+
if (tokens.length === 0) return { type: "js", fn, reason: "empty token stream" };
|
|
360
382
|
const parser = new Parser(tokens, paramName, fn);
|
|
361
383
|
const ast = parser.parseExpression();
|
|
362
|
-
if (!ast) return { type: "js", fn };
|
|
363
|
-
if (parser.pos < tokens.length) return { type: "js", fn };
|
|
384
|
+
if (!ast) return { type: "js", fn, reason: "could not parse expression" };
|
|
385
|
+
if (parser.pos < tokens.length) return { type: "js", fn, reason: "unexpected tokens after expression" };
|
|
364
386
|
const where = compileNode(ast);
|
|
365
|
-
if (!where) return { type: "js", fn };
|
|
387
|
+
if (!where) return { type: "js", fn, reason: "could not compile to SQL" };
|
|
366
388
|
return { type: "sql", where };
|
|
367
|
-
} catch {
|
|
368
|
-
return { type: "js", fn };
|
|
389
|
+
} catch (err) {
|
|
390
|
+
return { type: "js", fn, reason: `compilation error: ${err?.message || "unknown"}` };
|
|
369
391
|
}
|
|
370
392
|
}
|
|
371
393
|
function extractParamName(source) {
|
|
@@ -842,6 +864,9 @@ var Query = class _Query {
|
|
|
842
864
|
_limit;
|
|
843
865
|
_offset;
|
|
844
866
|
_config;
|
|
867
|
+
/** @internal Pre-compiled WHERE clause (bypasses predicate compiler). Used by Table.get(). */
|
|
868
|
+
_rawWhere;
|
|
869
|
+
_rawWhereParams;
|
|
845
870
|
/** @internal Post-process transform applied after row deserialization. */
|
|
846
871
|
_postProcess;
|
|
847
872
|
constructor(config, options) {
|
|
@@ -852,6 +877,8 @@ var Query = class _Query {
|
|
|
852
877
|
this._limit = options?.limit;
|
|
853
878
|
this._offset = options?.offset;
|
|
854
879
|
this._postProcess = options?.postProcess;
|
|
880
|
+
this._rawWhere = options?.rawWhere;
|
|
881
|
+
this._rawWhereParams = options?.rawWhereParams;
|
|
855
882
|
}
|
|
856
883
|
_clone(overrides) {
|
|
857
884
|
return new _Query(this._config, {
|
|
@@ -860,7 +887,9 @@ var Query = class _Query {
|
|
|
860
887
|
reversed: overrides.reversed ?? this._reversed,
|
|
861
888
|
limit: overrides.limit ?? this._limit,
|
|
862
889
|
offset: overrides.offset ?? this._offset,
|
|
863
|
-
postProcess: overrides.postProcess
|
|
890
|
+
postProcess: overrides.postProcess,
|
|
891
|
+
rawWhere: this._rawWhere,
|
|
892
|
+
rawWhereParams: this._rawWhereParams
|
|
864
893
|
});
|
|
865
894
|
}
|
|
866
895
|
// -------------------------------------------------------------------------
|
|
@@ -897,33 +926,16 @@ var Query = class _Query {
|
|
|
897
926
|
postProcess: (rows) => rows[0] ?? null
|
|
898
927
|
});
|
|
899
928
|
}
|
|
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;
|
|
929
|
+
count() {
|
|
930
|
+
return this._clone({
|
|
931
|
+
postProcess: (rows) => rows.length
|
|
932
|
+
});
|
|
913
933
|
}
|
|
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;
|
|
934
|
+
some() {
|
|
935
|
+
return this._clone({
|
|
936
|
+
limit: 1,
|
|
937
|
+
postProcess: (rows) => rows.length > 0
|
|
938
|
+
});
|
|
927
939
|
}
|
|
928
940
|
async every() {
|
|
929
941
|
const compiled = this._compilePredicates();
|
|
@@ -950,19 +962,19 @@ var Query = class _Query {
|
|
|
950
962
|
max(accessor) {
|
|
951
963
|
return this.sortBy(accessor).reverse().first();
|
|
952
964
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
965
|
+
groupBy(accessor) {
|
|
966
|
+
return this._clone({
|
|
967
|
+
postProcess: (rows) => {
|
|
968
|
+
const map = /* @__PURE__ */ new Map();
|
|
969
|
+
for (const row of rows) {
|
|
970
|
+
const key = accessor(row);
|
|
971
|
+
const group = map.get(key);
|
|
972
|
+
if (group) group.push(row);
|
|
973
|
+
else map.set(key, [row]);
|
|
974
|
+
}
|
|
975
|
+
return map;
|
|
963
976
|
}
|
|
964
|
-
}
|
|
965
|
-
return map;
|
|
977
|
+
});
|
|
966
978
|
}
|
|
967
979
|
// -------------------------------------------------------------------------
|
|
968
980
|
// Batch compilation — used by db.batch() to extract SQL without executing
|
|
@@ -976,6 +988,16 @@ var Query = class _Query {
|
|
|
976
988
|
* all rows and this query can filter them in JS post-fetch.
|
|
977
989
|
*/
|
|
978
990
|
_compile() {
|
|
991
|
+
if (this._rawWhere) {
|
|
992
|
+
const query = buildSelect(this._config.tableName, {
|
|
993
|
+
where: this._rawWhere,
|
|
994
|
+
whereParams: this._rawWhereParams,
|
|
995
|
+
orderBy: void 0,
|
|
996
|
+
limit: this._limit,
|
|
997
|
+
offset: this._offset
|
|
998
|
+
});
|
|
999
|
+
return { type: "query", query, fallbackQuery: null, config: this._config, postProcess: this._postProcess };
|
|
1000
|
+
}
|
|
979
1001
|
const compiled = this._compilePredicates();
|
|
980
1002
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
981
1003
|
if (compiled.allSql) {
|
|
@@ -1054,6 +1076,21 @@ var Query = class _Query {
|
|
|
1054
1076
|
// Execution internals
|
|
1055
1077
|
// -------------------------------------------------------------------------
|
|
1056
1078
|
async _execute() {
|
|
1079
|
+
if (this._rawWhere) {
|
|
1080
|
+
const query = buildSelect(this._config.tableName, {
|
|
1081
|
+
where: this._rawWhere,
|
|
1082
|
+
whereParams: this._rawWhereParams,
|
|
1083
|
+
limit: this._limit,
|
|
1084
|
+
offset: this._offset
|
|
1085
|
+
});
|
|
1086
|
+
const results = await this._config.executeBatch([query]);
|
|
1087
|
+
return results[0].rows.map(
|
|
1088
|
+
(row) => deserializeRow(
|
|
1089
|
+
row,
|
|
1090
|
+
this._config.columns
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1057
1094
|
const compiled = this._compilePredicates();
|
|
1058
1095
|
if (compiled.allSql) {
|
|
1059
1096
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
@@ -1104,9 +1141,12 @@ var Query = class _Query {
|
|
|
1104
1141
|
}
|
|
1105
1142
|
async _fetchAndFilterInJs(compiled) {
|
|
1106
1143
|
const allRows = await this._fetchAllRows();
|
|
1107
|
-
|
|
1144
|
+
const jsFallbacks = compiled.compiled.filter((c) => c.type === "js");
|
|
1145
|
+
if (jsFallbacks.length > 0) {
|
|
1146
|
+
const reasons = jsFallbacks.map((c) => c.type === "js" ? c.reason : void 0).filter(Boolean);
|
|
1147
|
+
const reasonSuffix = reasons.length > 0 ? ` (${reasons.join("; ")})` : "";
|
|
1108
1148
|
console.warn(
|
|
1109
|
-
`[mindstudio] Filter on ${this._config.tableName} could not be compiled to SQL \u2014 scanning ${allRows.length} rows in JS`
|
|
1149
|
+
`[mindstudio] Filter on '${this._config.tableName}' could not be compiled to SQL${reasonSuffix} \u2014 scanning ${allRows.length} rows in JS`
|
|
1110
1150
|
);
|
|
1111
1151
|
}
|
|
1112
1152
|
return allRows.filter(
|
|
@@ -1179,8 +1219,10 @@ var Mutation = class _Mutation {
|
|
|
1179
1219
|
*/
|
|
1180
1220
|
_compile() {
|
|
1181
1221
|
if (this._executor) {
|
|
1182
|
-
throw new
|
|
1183
|
-
"This operation cannot be batched (e.g. removeAll with a predicate
|
|
1222
|
+
throw new MindStudioError(
|
|
1223
|
+
"This operation cannot be batched (e.g. removeAll with a JS-fallback predicate). Await it separately instead of passing to db.batch().",
|
|
1224
|
+
"not_batchable",
|
|
1225
|
+
400
|
|
1184
1226
|
);
|
|
1185
1227
|
}
|
|
1186
1228
|
return {
|
|
@@ -1217,58 +1259,61 @@ var Table = class {
|
|
|
1217
1259
|
this._config = config;
|
|
1218
1260
|
}
|
|
1219
1261
|
// -------------------------------------------------------------------------
|
|
1220
|
-
// Reads —
|
|
1262
|
+
// Reads — all return batchable Query objects (lazy until awaited)
|
|
1221
1263
|
// -------------------------------------------------------------------------
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1264
|
+
/** Get a single row by ID. Returns null if not found. */
|
|
1265
|
+
get(id) {
|
|
1266
|
+
return new Query(this._config, {
|
|
1267
|
+
rawWhere: "id = ?",
|
|
1268
|
+
rawWhereParams: [id],
|
|
1269
|
+
limit: 1,
|
|
1270
|
+
postProcess: (rows) => rows[0] ?? null
|
|
1227
1271
|
});
|
|
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
1272
|
}
|
|
1235
|
-
|
|
1273
|
+
/** Find the first row matching a predicate. Returns null if none match. */
|
|
1274
|
+
findOne(predicate) {
|
|
1236
1275
|
return this.filter(predicate).first();
|
|
1237
1276
|
}
|
|
1238
|
-
|
|
1277
|
+
count(predicate) {
|
|
1239
1278
|
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;
|
|
1279
|
+
return this.toArray().count();
|
|
1244
1280
|
}
|
|
1245
|
-
|
|
1281
|
+
/** True if any row matches the predicate. */
|
|
1282
|
+
some(predicate) {
|
|
1246
1283
|
return this.filter(predicate).some();
|
|
1247
1284
|
}
|
|
1285
|
+
/** True if all rows match the predicate. */
|
|
1248
1286
|
async every(predicate) {
|
|
1249
1287
|
return this.filter(predicate).every();
|
|
1250
1288
|
}
|
|
1289
|
+
/** True if the table has zero rows. */
|
|
1251
1290
|
async isEmpty() {
|
|
1252
1291
|
const query = buildExists(this._config.tableName, void 0, void 0, true);
|
|
1253
1292
|
const results = await this._config.executeBatch([query]);
|
|
1254
1293
|
const row = results[0]?.rows[0];
|
|
1255
1294
|
return row?.result === 1;
|
|
1256
1295
|
}
|
|
1257
|
-
|
|
1296
|
+
/** Row with the minimum value for a field, or null if table is empty. */
|
|
1297
|
+
min(accessor) {
|
|
1258
1298
|
return this.sortBy(accessor).first();
|
|
1259
1299
|
}
|
|
1260
|
-
|
|
1300
|
+
/** Row with the maximum value for a field, or null if table is empty. */
|
|
1301
|
+
max(accessor) {
|
|
1261
1302
|
return this.sortBy(accessor).reverse().first();
|
|
1262
1303
|
}
|
|
1263
|
-
|
|
1304
|
+
/** Group rows by a field. Returns a Map. */
|
|
1305
|
+
groupBy(accessor) {
|
|
1264
1306
|
return new Query(this._config).groupBy(accessor);
|
|
1265
1307
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1308
|
+
/** Get all rows as an array. */
|
|
1309
|
+
toArray() {
|
|
1310
|
+
return new Query(this._config);
|
|
1311
|
+
}
|
|
1312
|
+
/** Filter rows by a predicate. Returns a chainable Query. */
|
|
1269
1313
|
filter(predicate) {
|
|
1270
1314
|
return new Query(this._config).filter(predicate);
|
|
1271
1315
|
}
|
|
1316
|
+
/** Sort rows by a field. Returns a chainable Query. */
|
|
1272
1317
|
sortBy(accessor) {
|
|
1273
1318
|
return new Query(this._config).sortBy(accessor);
|
|
1274
1319
|
}
|
|
@@ -1295,7 +1340,11 @@ var Table = class {
|
|
|
1295
1340
|
this._config.columns
|
|
1296
1341
|
);
|
|
1297
1342
|
}
|
|
1298
|
-
|
|
1343
|
+
throw new MindStudioError(
|
|
1344
|
+
`Insert into '${this._config.tableName}' succeeded but returned no row. This may indicate a constraint violation.`,
|
|
1345
|
+
"insert_failed",
|
|
1346
|
+
500
|
|
1347
|
+
);
|
|
1299
1348
|
});
|
|
1300
1349
|
const result = isArray ? rows : rows[0];
|
|
1301
1350
|
this._syncRolesIfNeeded(
|
|
@@ -1319,6 +1368,13 @@ var Table = class {
|
|
|
1319
1368
|
this._config.columns
|
|
1320
1369
|
);
|
|
1321
1370
|
return new Mutation(this._config, [query], (results) => {
|
|
1371
|
+
if (!results[0]?.rows[0]) {
|
|
1372
|
+
throw new MindStudioError(
|
|
1373
|
+
`Row not found: no row with ID '${id}' in table '${this._config.tableName}'`,
|
|
1374
|
+
"row_not_found",
|
|
1375
|
+
404
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1322
1378
|
const result = deserializeRow(
|
|
1323
1379
|
results[0].rows[0],
|
|
1324
1380
|
this._config.columns
|
|
@@ -1333,7 +1389,9 @@ var Table = class {
|
|
|
1333
1389
|
}
|
|
1334
1390
|
remove(id) {
|
|
1335
1391
|
const query = buildDelete(this._config.tableName, `id = ?`, [id]);
|
|
1336
|
-
return new Mutation(this._config, [query], () =>
|
|
1392
|
+
return new Mutation(this._config, [query], (results) => ({
|
|
1393
|
+
deleted: results[0].changes > 0
|
|
1394
|
+
}));
|
|
1337
1395
|
}
|
|
1338
1396
|
/**
|
|
1339
1397
|
* Remove all rows matching a predicate. Returns the count removed.
|
|
@@ -1367,7 +1425,7 @@ var Table = class {
|
|
|
1367
1425
|
}
|
|
1368
1426
|
clear() {
|
|
1369
1427
|
const query = buildDelete(this._config.tableName);
|
|
1370
|
-
return new Mutation(this._config, [query], () =>
|
|
1428
|
+
return new Mutation(this._config, [query], (results) => results[0].changes);
|
|
1371
1429
|
}
|
|
1372
1430
|
/**
|
|
1373
1431
|
* Insert a row, or update it if a row with the same unique key already
|
|
@@ -1385,6 +1443,15 @@ var Table = class {
|
|
|
1385
1443
|
this._validateUniqueConstraint(conflictColumns);
|
|
1386
1444
|
const withDefaults = this._config.defaults ? { ...this._config.defaults, ...data } : data;
|
|
1387
1445
|
this._checkManagedColumns(withDefaults);
|
|
1446
|
+
for (const col of conflictColumns) {
|
|
1447
|
+
if (!(col in withDefaults)) {
|
|
1448
|
+
throw new MindStudioError(
|
|
1449
|
+
`Upsert on ${this._config.tableName} requires "${col}" in data (conflict key)`,
|
|
1450
|
+
"missing_conflict_key",
|
|
1451
|
+
400
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1388
1455
|
const query = buildUpsert(
|
|
1389
1456
|
this._config.tableName,
|
|
1390
1457
|
withDefaults,
|
|
@@ -1392,6 +1459,13 @@ var Table = class {
|
|
|
1392
1459
|
this._config.columns
|
|
1393
1460
|
);
|
|
1394
1461
|
return new Mutation(this._config, [query], (results) => {
|
|
1462
|
+
if (!results[0]?.rows[0]) {
|
|
1463
|
+
throw new MindStudioError(
|
|
1464
|
+
`Upsert into ${this._config.tableName} returned no row`,
|
|
1465
|
+
"upsert_failed",
|
|
1466
|
+
500
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1395
1469
|
const result = deserializeRow(
|
|
1396
1470
|
results[0].rows[0],
|
|
1397
1471
|
this._config.columns
|
|
@@ -1543,7 +1617,7 @@ function createDb(databases, executeBatch, authConfig, syncRoles) {
|
|
|
1543
1617
|
if (c.type === "query") {
|
|
1544
1618
|
if (!c.query && c.predicates?.length) {
|
|
1545
1619
|
console.warn(
|
|
1546
|
-
`[mindstudio] db.batch(): filter on ${c.config.tableName} could not be compiled to SQL \u2014 processing in JS`
|
|
1620
|
+
`[mindstudio] db.batch(): filter on '${c.config.tableName}' could not be compiled to SQL \u2014 processing in JS`
|
|
1547
1621
|
);
|
|
1548
1622
|
}
|
|
1549
1623
|
return Query._processResults(results[0], c);
|
|
@@ -3018,9 +3092,14 @@ var stepMetadata = {
|
|
|
3018
3092
|
},
|
|
3019
3093
|
"sendEmail": {
|
|
3020
3094
|
stepType: "sendEmail",
|
|
3021
|
-
description: "Send an email to one or more
|
|
3022
|
-
usageNotes:
|
|
3023
|
-
|
|
3095
|
+
description: "Send an email to one or more recipient addresses.",
|
|
3096
|
+
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.
|
|
3097
|
+
- 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.
|
|
3098
|
+
- If the body is a URL to a hosted HTML file on the CDN, the HTML is fetched and used as the email body.
|
|
3099
|
+
- When generateHtml is enabled, the body text is converted to a styled HTML email using an AI model.
|
|
3100
|
+
- connectionId can be a comma-separated list to send to multiple recipients.
|
|
3101
|
+
- The special connectionId "trigger_email" uses the email address that triggered the workflow.`,
|
|
3102
|
+
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
3103
|
outputSchema: { "type": "object", "properties": { "recipients": { "type": "array", "items": { "type": "string" }, "description": "Email addresses the message was sent to" } }, "required": ["recipients"] }
|
|
3025
3104
|
},
|
|
3026
3105
|
"sendGmailDraft": {
|
|
@@ -3342,7 +3421,7 @@ var MindStudioAgent = class {
|
|
|
3342
3421
|
const res = await fetch(data.outputUrl);
|
|
3343
3422
|
if (!res.ok) {
|
|
3344
3423
|
throw new MindStudioError(
|
|
3345
|
-
`Failed to fetch output from S3: ${res.status} ${res.statusText}`,
|
|
3424
|
+
`Failed to fetch ${stepType} output from S3: ${res.status} ${res.statusText}`,
|
|
3346
3425
|
"output_fetch_error",
|
|
3347
3426
|
res.status
|
|
3348
3427
|
);
|
|
@@ -3405,13 +3484,27 @@ var MindStudioAgent = class {
|
|
|
3405
3484
|
this._httpConfig.rateLimiter.updateFromHeaders(res.headers);
|
|
3406
3485
|
if (!res.ok) {
|
|
3407
3486
|
this._httpConfig.rateLimiter.release();
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
res.
|
|
3413
|
-
|
|
3414
|
-
|
|
3487
|
+
let message = `${res.status} ${res.statusText}`;
|
|
3488
|
+
let code = "api_error";
|
|
3489
|
+
let details;
|
|
3490
|
+
try {
|
|
3491
|
+
const text = await res.text();
|
|
3492
|
+
try {
|
|
3493
|
+
const body2 = JSON.parse(text);
|
|
3494
|
+
details = body2;
|
|
3495
|
+
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);
|
|
3496
|
+
if (errMsg) message = errMsg;
|
|
3497
|
+
else if (body2.error || body2.message || body2.details) {
|
|
3498
|
+
message = JSON.stringify(body2.error ?? body2.message ?? body2.details);
|
|
3499
|
+
}
|
|
3500
|
+
if (body2.code) code = body2.code;
|
|
3501
|
+
} catch {
|
|
3502
|
+
const stripped = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
3503
|
+
if (stripped) message = stripped.slice(0, 200);
|
|
3504
|
+
}
|
|
3505
|
+
} catch {
|
|
3506
|
+
}
|
|
3507
|
+
throw new MindStudioError(`[${stepType}] ${message}`, code, res.status, details);
|
|
3415
3508
|
}
|
|
3416
3509
|
const headers = res.headers;
|
|
3417
3510
|
try {
|
|
@@ -3444,7 +3537,7 @@ var MindStudioAgent = class {
|
|
|
3444
3537
|
};
|
|
3445
3538
|
} else if (event.type === "error") {
|
|
3446
3539
|
throw new MindStudioError(
|
|
3447
|
-
event.error || "Step execution failed"
|
|
3540
|
+
`[${stepType}] ${event.error || "Step execution failed"}`,
|
|
3448
3541
|
"step_error",
|
|
3449
3542
|
500
|
|
3450
3543
|
);
|
|
@@ -3483,7 +3576,7 @@ var MindStudioAgent = class {
|
|
|
3483
3576
|
}
|
|
3484
3577
|
if (!doneEvent) {
|
|
3485
3578
|
throw new MindStudioError(
|
|
3486
|
-
|
|
3579
|
+
`[${stepType}] Stream ended unexpectedly without completing. The step execution may have been interrupted.`,
|
|
3487
3580
|
"stream_error",
|
|
3488
3581
|
500
|
|
3489
3582
|
);
|
|
@@ -3495,7 +3588,7 @@ var MindStudioAgent = class {
|
|
|
3495
3588
|
const s3Res = await fetch(doneEvent.outputUrl);
|
|
3496
3589
|
if (!s3Res.ok) {
|
|
3497
3590
|
throw new MindStudioError(
|
|
3498
|
-
`Failed to fetch output from S3: ${s3Res.status} ${s3Res.statusText}`,
|
|
3591
|
+
`Failed to fetch ${stepType} output from S3: ${s3Res.status} ${s3Res.statusText}`,
|
|
3499
3592
|
"output_fetch_error",
|
|
3500
3593
|
s3Res.status
|
|
3501
3594
|
);
|
|
@@ -3856,7 +3949,7 @@ var MindStudioAgent = class {
|
|
|
3856
3949
|
}
|
|
3857
3950
|
if (!this._auth) {
|
|
3858
3951
|
throw new MindStudioError(
|
|
3859
|
-
"Auth context not
|
|
3952
|
+
"Auth context not loaded. Call `await agent.ensureContext()` first, or perform any db operation (which auto-loads context).",
|
|
3860
3953
|
"context_not_loaded",
|
|
3861
3954
|
400
|
|
3862
3955
|
);
|
|
@@ -4030,13 +4123,12 @@ var MindStudioAgent = class {
|
|
|
4030
4123
|
if (!res.ok) {
|
|
4031
4124
|
const text = await res.text().catch(() => "");
|
|
4032
4125
|
console.warn(
|
|
4033
|
-
`[mindstudio]
|
|
4126
|
+
`[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
4127
|
);
|
|
4035
4128
|
}
|
|
4036
4129
|
} catch (err) {
|
|
4037
4130
|
console.warn(
|
|
4038
|
-
`[mindstudio]
|
|
4039
|
-
err
|
|
4131
|
+
`[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
4132
|
);
|
|
4041
4133
|
}
|
|
4042
4134
|
}
|