@cicctencent/agent-server 0.2.57 → 0.2.59

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.
Files changed (55) hide show
  1. package/dist/cjs/automation/node-task-runtime.d.ts.map +1 -1
  2. package/dist/cjs/automation/node-task-runtime.js +2 -0
  3. package/dist/cjs/automation/node-task-runtime.js.map +1 -1
  4. package/dist/cjs/doc-generator/report-renderer.d.ts.map +1 -1
  5. package/dist/cjs/doc-generator/report-renderer.js +33 -7
  6. package/dist/cjs/doc-generator/report-renderer.js.map +1 -1
  7. package/dist/cjs/index.d.ts +3 -0
  8. package/dist/cjs/index.d.ts.map +1 -1
  9. package/dist/cjs/index.js +9 -3
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/prompt/prompt-builder.d.ts +1 -1
  12. package/dist/cjs/prompt/prompt-builder.d.ts.map +1 -1
  13. package/dist/cjs/prompt/prompt-builder.js +1 -0
  14. package/dist/cjs/prompt/prompt-builder.js.map +1 -1
  15. package/dist/cjs/prompt/shared-fragments.js +1 -1
  16. package/dist/cjs/prompt/shared-fragments.js.map +1 -1
  17. package/dist/cjs/sandbox/local-sandbox.d.ts +2 -0
  18. package/dist/cjs/sandbox/local-sandbox.d.ts.map +1 -1
  19. package/dist/cjs/sandbox/local-sandbox.js +34 -0
  20. package/dist/cjs/sandbox/local-sandbox.js.map +1 -1
  21. package/dist/cjs/sandbox/sandbox-prelude.d.ts +54 -0
  22. package/dist/cjs/sandbox/sandbox-prelude.d.ts.map +1 -0
  23. package/dist/cjs/sandbox/sandbox-prelude.js +645 -0
  24. package/dist/cjs/sandbox/sandbox-prelude.js.map +1 -0
  25. package/dist/cjs/tool/tools/sandbox.d.ts.map +1 -1
  26. package/dist/cjs/tool/tools/sandbox.js +13 -1
  27. package/dist/cjs/tool/tools/sandbox.js.map +1 -1
  28. package/dist/esm/automation/node-task-runtime.d.ts.map +1 -1
  29. package/dist/esm/automation/node-task-runtime.js +2 -0
  30. package/dist/esm/automation/node-task-runtime.js.map +1 -1
  31. package/dist/esm/doc-generator/report-renderer.d.ts.map +1 -1
  32. package/dist/esm/doc-generator/report-renderer.js +33 -7
  33. package/dist/esm/doc-generator/report-renderer.js.map +1 -1
  34. package/dist/esm/index.d.ts +3 -0
  35. package/dist/esm/index.d.ts.map +1 -1
  36. package/dist/esm/index.js +2 -0
  37. package/dist/esm/index.js.map +1 -1
  38. package/dist/esm/prompt/prompt-builder.d.ts +1 -1
  39. package/dist/esm/prompt/prompt-builder.d.ts.map +1 -1
  40. package/dist/esm/prompt/prompt-builder.js +1 -0
  41. package/dist/esm/prompt/prompt-builder.js.map +1 -1
  42. package/dist/esm/prompt/shared-fragments.js +1 -1
  43. package/dist/esm/prompt/shared-fragments.js.map +1 -1
  44. package/dist/esm/sandbox/local-sandbox.d.ts +2 -0
  45. package/dist/esm/sandbox/local-sandbox.d.ts.map +1 -1
  46. package/dist/esm/sandbox/local-sandbox.js +34 -0
  47. package/dist/esm/sandbox/local-sandbox.js.map +1 -1
  48. package/dist/esm/sandbox/sandbox-prelude.d.ts +54 -0
  49. package/dist/esm/sandbox/sandbox-prelude.d.ts.map +1 -0
  50. package/dist/esm/sandbox/sandbox-prelude.js +639 -0
  51. package/dist/esm/sandbox/sandbox-prelude.js.map +1 -0
  52. package/dist/esm/tool/tools/sandbox.d.ts.map +1 -1
  53. package/dist/esm/tool/tools/sandbox.js +13 -1
  54. package/dist/esm/tool/tools/sandbox.js.map +1 -1
  55. package/package.json +2 -2
@@ -0,0 +1,645 @@
1
+ "use strict";
2
+ /**
3
+ * @file Sandbox Prelude - 沙箱基础能力库
4
+ * @description 注入到每个沙箱 _scripts/_prelude.cjs 的通用工具模块
5
+ * 通过 runScript preamble 自动暴露为 global.sb
6
+ * 让 LLM 只需编写业务逻辑,无需重复实现常用功能
7
+ *
8
+ * 可用能力:
9
+ * - HTTP: sb.fetch / sb.get / sb.post / sb.put / sb.head / sb.del
10
+ * - File I/O: sb.readJson / sb.writeJson / sb.readText / sb.writeText / sb.appendText / sb.readCsv / sb.writeCsv / sb.exists / sb.listDir
11
+ * - Data: sb.stats / sb.sum / sb.avg / sb.groupBy / sb.sortBy / sb.unique / sb.chunk / sb.range / sb.pick / sb.omit / sb.flatten
12
+ * - Date: sb.formatDate / sb.parseDate / sb.now / sb.daysAgo / sb.daysLater / sb.dateDiff
13
+ * - String: sb.template / sb.slugify / sb.truncate / sb.pad
14
+ * - Table: sb.markdownTable / sb.htmlTable
15
+ * - Logger: sb.log.info / sb.log.warn / sb.log.error / sb.log.debug
16
+ *
17
+ * == 扩展机制 ==
18
+ * 基础能力可通过「扩展」机制注册,无需修改本文件:
19
+ * 1. 全局注册(作用于所有沙箱):
20
+ * import { registerSandboxPreludeExtension } from '@cicctencent/agent-server';
21
+ * registerSandboxPreludeExtension('myUtil', `
22
+ * module.exports = {
23
+ * greet(name) { return 'Hello, ' + name; },
24
+ * };
25
+ * `);
26
+ * 2. 会话级注册(仅作用于单个沙箱):
27
+ * sandboxManager.create(sessionId, { preludeExtensions: { myUtil: 'module.exports = { ... };' } });
28
+ * 扩展模块需通过 module.exports 导出对象,prelude 会在加载时将其合并进 global.sb
29
+ * (同名 key 会覆盖核心能力,便于定制)。扩展文件位于 _scripts/_prelude_ext/<name>.cjs。
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.SANDBOX_PRELUDE_SOURCE = void 0;
33
+ exports.registerSandboxPreludeExtension = registerSandboxPreludeExtension;
34
+ exports.getSandboxPreludeExtensions = getSandboxPreludeExtensions;
35
+ exports.clearSandboxPreludeExtensions = clearSandboxPreludeExtensions;
36
+ const SANDBOX_PRELUDE_EXTENSIONS = [];
37
+ /**
38
+ * 注册一个沙箱基础能力扩展(全局生效,作用于之后创建的所有沙箱)
39
+ * @param name 扩展名,仅允许 [a-zA-Z0-9_-]
40
+ * @param source 扩展 CommonJS 源码,需通过 module.exports 导出对象
41
+ * @example
42
+ * registerSandboxPreludeExtension('csv', `module.exports = { parse(text){ return text.split('\\n'); } };`);
43
+ */
44
+ function registerSandboxPreludeExtension(name, source) {
45
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
46
+ throw new Error(`Invalid prelude extension name "${name}". Only [a-zA-Z0-9_-] allowed.`);
47
+ }
48
+ if (typeof source !== 'string' || !source.trim()) {
49
+ throw new Error(`Prelude extension "${name}" source must be a non-empty string.`);
50
+ }
51
+ const idx = SANDBOX_PRELUDE_EXTENSIONS.findIndex((e) => e.name === name);
52
+ if (idx >= 0) {
53
+ SANDBOX_PRELUDE_EXTENSIONS[idx] = { name, source };
54
+ }
55
+ else {
56
+ SANDBOX_PRELUDE_EXTENSIONS.push({ name, source });
57
+ }
58
+ }
59
+ /** 获取当前注册的所有扩展 */
60
+ function getSandboxPreludeExtensions() {
61
+ return SANDBOX_PRELUDE_EXTENSIONS.map((e) => ({ ...e }));
62
+ }
63
+ /** 清空所有已注册的扩展(主要用于测试) */
64
+ function clearSandboxPreludeExtensions() {
65
+ SANDBOX_PRELUDE_EXTENSIONS.length = 0;
66
+ }
67
+ /**
68
+ * _prelude.cjs 源代码
69
+ * 在沙箱初始化时写入 _scripts/_prelude.cjs
70
+ * 通过 runScript Node.js preamble 自动 require 为 global.sb
71
+ */
72
+ exports.SANDBOX_PRELUDE_SOURCE = `'use strict';
73
+ // ============================================================
74
+ // Sandbox Prelude - Auto-loaded as global \`sb\` in Node.js scripts
75
+ // Common utilities so you only write business logic.
76
+ // Loads extensions from _prelude_ext/ at require time.
77
+ // ============================================================
78
+
79
+ const fs = require('fs');
80
+ const path = require('path');
81
+ const https = require('https');
82
+ const http = require('http');
83
+ const { URL } = require('url');
84
+
85
+ // ===== Helpers =====
86
+
87
+ // Safe JSON stringify (handles circular references)
88
+ function _safeStringify(v) {
89
+ try { return JSON.stringify(v); } catch (e) { return String(v); }
90
+ }
91
+
92
+ // ===== HTTP =====
93
+
94
+ /**
95
+ * fetch - HTTP request with timeout, JSON parsing and redirect following
96
+ * @param {string} url - Request URL
97
+ * @param {object} options - { method, headers, body, json, params, timeout, maxRedirects }
98
+ * @returns {Promise<{status, headers, data, text}>}
99
+ */
100
+ async function fetch(url, options = {}, _depth = 0) {
101
+ const {
102
+ method = 'GET',
103
+ headers = {},
104
+ body,
105
+ json,
106
+ timeout = 30000,
107
+ params,
108
+ maxRedirects = 3,
109
+ } = options;
110
+ let targetUrl = url;
111
+ if (params) {
112
+ const u = new URL(targetUrl);
113
+ for (const [k, v] of Object.entries(params)) u.searchParams.set(k, String(v));
114
+ targetUrl = u.toString();
115
+ }
116
+ const u = new URL(targetUrl);
117
+ const lib = u.protocol === 'https:' ? https : http;
118
+ const reqHeaders = { 'User-Agent': 'workai-sandbox/1.0', ...headers };
119
+ let reqBody = body;
120
+ if (json !== undefined) {
121
+ reqBody = JSON.stringify(json);
122
+ reqHeaders['Content-Type'] = reqHeaders['Content-Type'] || 'application/json';
123
+ }
124
+ return new Promise((resolve, reject) => {
125
+ const req = lib.request(u, { method, headers: reqHeaders, timeout }, (res) => {
126
+ const status = res.statusCode;
127
+ // Follow redirects (301/302/303/307/308)
128
+ if (
129
+ (status === 301 || status === 302 || status === 303 || status === 307 || status === 308) &&
130
+ res.headers.location &&
131
+ _depth < maxRedirects
132
+ ) {
133
+ res.resume(); // discard response body
134
+ const nextUrl = new URL(res.headers.location, targetUrl).toString();
135
+ const nextMethod = status === 303 ? 'GET' : method;
136
+ const nextBody = status === 303 ? undefined : body;
137
+ const nextJson = status === 303 ? undefined : json;
138
+ resolve(fetch(nextUrl, { ...options, method: nextMethod, body: nextBody, json: nextJson }, _depth + 1));
139
+ return;
140
+ }
141
+ const chunks = [];
142
+ res.on('data', (c) => chunks.push(c));
143
+ res.on('end', () => {
144
+ const buf = Buffer.concat(chunks);
145
+ const text = buf.toString('utf-8');
146
+ let data = text;
147
+ const ct = (res.headers['content-type'] || '').toLowerCase();
148
+ if (ct.includes('application/json') || ct.includes('+json')) {
149
+ try { data = JSON.parse(text); } catch (e) { /* keep text */ }
150
+ }
151
+ resolve({ status: status, headers: res.headers, data, text });
152
+ });
153
+ });
154
+ req.on('error', reject);
155
+ req.on('timeout', () => { req.destroy(new Error('Request timeout after ' + timeout + 'ms')); });
156
+ if (reqBody) req.write(reqBody);
157
+ req.end();
158
+ });
159
+ }
160
+
161
+ /** GET request shorthand */
162
+ async function get(url, options = {}) {
163
+ return fetch(url, { ...options, method: 'GET' });
164
+ }
165
+
166
+ /** POST request shorthand (auto-JSON for object bodies) */
167
+ async function post(url, body, options = {}) {
168
+ const isJson = typeof body === 'object' && body !== null && !Buffer.isBuffer(body);
169
+ return fetch(url, {
170
+ ...options,
171
+ method: 'POST',
172
+ json: isJson ? body : undefined,
173
+ body: isJson ? undefined : body,
174
+ });
175
+ }
176
+
177
+ /** PUT request shorthand (auto-JSON for object bodies) */
178
+ async function put(url, body, options = {}) {
179
+ const isJson = typeof body === 'object' && body !== null && !Buffer.isBuffer(body);
180
+ return fetch(url, {
181
+ ...options,
182
+ method: 'PUT',
183
+ json: isJson ? body : undefined,
184
+ body: isJson ? undefined : body,
185
+ });
186
+ }
187
+
188
+ /** HEAD request shorthand */
189
+ async function head(url, options = {}) {
190
+ return fetch(url, { ...options, method: 'HEAD' });
191
+ }
192
+
193
+ /** DELETE request shorthand */
194
+ async function del(url, options = {}) {
195
+ return fetch(url, { ...options, method: 'DELETE' });
196
+ }
197
+
198
+ // ===== File I/O =====
199
+
200
+ function readJson(file) {
201
+ const content = fs.readFileSync(file, 'utf-8');
202
+ return JSON.parse(content);
203
+ }
204
+
205
+ function writeJson(file, data, indent = 2) {
206
+ const dir = path.dirname(file);
207
+ if (dir && dir !== '.') try { fs.mkdirSync(dir, { recursive: true }); } catch (e) {}
208
+ fs.writeFileSync(file, JSON.stringify(data, null, indent), 'utf-8');
209
+ }
210
+
211
+ function readText(file, encoding = 'utf-8') {
212
+ return fs.readFileSync(file, encoding);
213
+ }
214
+
215
+ function writeText(file, content) {
216
+ const dir = path.dirname(file);
217
+ if (dir && dir !== '.') try { fs.mkdirSync(dir, { recursive: true }); } catch (e) {}
218
+ fs.writeFileSync(file, content, 'utf-8');
219
+ }
220
+
221
+ function appendText(file, content) {
222
+ const dir = path.dirname(file);
223
+ if (dir && dir !== '.') try { fs.mkdirSync(dir, { recursive: true }); } catch (e) {}
224
+ fs.appendFileSync(file, content, 'utf-8');
225
+ }
226
+
227
+ function exists(p) {
228
+ try { return fs.existsSync(p); } catch (e) { return false; }
229
+ }
230
+
231
+ function listDir(dir) {
232
+ return fs.readdirSync(dir).map(function (name) {
233
+ let stat;
234
+ try { stat = fs.statSync(path.join(dir, name)); } catch (e) { return name; }
235
+ return stat.isDirectory() ? name + '/' : name;
236
+ });
237
+ }
238
+
239
+ /**
240
+ * readCsv - parse CSV file into array of objects
241
+ * @param {string} file - CSV file path
242
+ * @param {object} opts - { delimiter, headers }
243
+ * headers: 自定义表头数组;若提供则首行被视为数据行,否则首行作为表头
244
+ * @returns {Array<object>}
245
+ */
246
+ function readCsv(file, opts = {}) {
247
+ const delimiter = opts.delimiter || ',';
248
+ const content = fs.readFileSync(file, 'utf-8');
249
+ const lines = content.split(/\\r?\\n/).filter(function (l) { return l.trim(); });
250
+ if (lines.length === 0) return [];
251
+ const parseLine = function (line) {
252
+ const result = [];
253
+ let cur = '';
254
+ let inQuote = false;
255
+ for (let i = 0; i < line.length; i++) {
256
+ const c = line[i];
257
+ if (c === '"') {
258
+ if (inQuote && line[i + 1] === '"') { cur += '"'; i++; }
259
+ else inQuote = !inQuote;
260
+ } else if (c === delimiter && !inQuote) {
261
+ result.push(cur); cur = '';
262
+ } else {
263
+ cur += c;
264
+ }
265
+ }
266
+ result.push(cur);
267
+ return result.map(function (s) { return s.trim(); });
268
+ };
269
+ const headers = opts.headers || parseLine(lines[0]);
270
+ const startIdx = opts.headers ? 0 : 1;
271
+ const rows = [];
272
+ for (let i = startIdx; i < lines.length; i++) {
273
+ const values = parseLine(lines[i]);
274
+ const row = {};
275
+ headers.forEach(function (h, j) { row[h] = values[j] || ''; });
276
+ rows.push(row);
277
+ }
278
+ return rows;
279
+ }
280
+
281
+ /**
282
+ * writeCsv - write array of objects to CSV file
283
+ * @param {string} file - Output CSV file path
284
+ * @param {Array<object>} rows - Data rows
285
+ * @param {object} opts - { headers, delimiter }
286
+ */
287
+ function writeCsv(file, rows, opts = {}) {
288
+ if (!rows || rows.length === 0) {
289
+ writeText(file, '');
290
+ return;
291
+ }
292
+ const delimiter = opts.delimiter || ',';
293
+ const headers = opts.headers || Object.keys(rows[0]);
294
+ const escapeCell = function (val) {
295
+ const s = String(val == null ? '' : val);
296
+ if (s.includes(delimiter) || s.includes('"') || s.includes('\\n')) {
297
+ return '"' + s.replace(/"/g, '""') + '"';
298
+ }
299
+ return s;
300
+ };
301
+ const lines = [headers.map(escapeCell).join(delimiter)];
302
+ for (const row of rows) {
303
+ lines.push(headers.map(function (h) { return escapeCell(row[h]); }).join(delimiter));
304
+ }
305
+ writeText(file, lines.join('\\n'));
306
+ }
307
+
308
+ // ===== Data Utilities =====
309
+
310
+ /** Calculate statistics: sum, avg, min, max, count, std */
311
+ function stats(arr) {
312
+ const nums = arr.map(Number).filter(function (n) { return !isNaN(n); });
313
+ if (nums.length === 0) return { count: 0, sum: 0, avg: 0, min: 0, max: 0, std: 0 };
314
+ const sumVal = nums.reduce(function (a, b) { return a + b; }, 0);
315
+ const avgVal = sumVal / nums.length;
316
+ const min = Math.min.apply(null, nums);
317
+ const max = Math.max.apply(null, nums);
318
+ const variance = nums.reduce(function (acc, n) { return acc + Math.pow(n - avgVal, 2); }, 0) / nums.length;
319
+ return { count: nums.length, sum: sumVal, avg: avgVal, min: min, max: max, std: Math.sqrt(variance) };
320
+ }
321
+
322
+ function sum(arr) {
323
+ return arr.reduce(function (a, b) { return a + (Number(b) || 0); }, 0);
324
+ }
325
+
326
+ function avg(arr) {
327
+ if (arr.length === 0) return 0;
328
+ return sum(arr) / arr.length;
329
+ }
330
+
331
+ function groupBy(arr, keyFn) {
332
+ const result = {};
333
+ for (const item of arr) {
334
+ const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn];
335
+ if (!result[key]) result[key] = [];
336
+ result[key].push(item);
337
+ }
338
+ return result;
339
+ }
340
+
341
+ function sortBy(arr, keyFn, desc) {
342
+ const fn = typeof keyFn === 'function' ? keyFn : function (x) { return x[keyFn]; };
343
+ return arr.slice().sort(function (a, b) {
344
+ const va = fn(a);
345
+ const vb = fn(b);
346
+ if (va < vb) return desc ? 1 : -1;
347
+ if (va > vb) return desc ? -1 : 1;
348
+ return 0;
349
+ });
350
+ }
351
+
352
+ function unique(arr, keyFn) {
353
+ if (!keyFn) {
354
+ return arr.filter(function (v, i, self) { return self.indexOf(v) === i; });
355
+ }
356
+ const fn = typeof keyFn === 'function' ? keyFn : function (x) { return x[keyFn]; };
357
+ const seen = new Set();
358
+ return arr.filter(function (x) {
359
+ const k = fn(x);
360
+ if (seen.has(k)) return false;
361
+ seen.add(k);
362
+ return true;
363
+ });
364
+ }
365
+
366
+ function chunk(arr, size) {
367
+ size = Math.max(1, size || 1);
368
+ const result = [];
369
+ for (let i = 0; i < arr.length; i += size) {
370
+ result.push(arr.slice(i, i + size));
371
+ }
372
+ return result;
373
+ }
374
+
375
+ function range(start, end, step) {
376
+ step = step || 1;
377
+ if (end === undefined) { end = start; start = 0; }
378
+ const result = [];
379
+ if (step > 0) {
380
+ for (let i = start; i < end; i += step) result.push(i);
381
+ } else {
382
+ for (let i = start; i > end; i += step) result.push(i);
383
+ }
384
+ return result;
385
+ }
386
+
387
+ function pick(obj, keys) {
388
+ const result = {};
389
+ for (const k of keys) {
390
+ if (k in obj) result[k] = obj[k];
391
+ }
392
+ return result;
393
+ }
394
+
395
+ function omit(obj, keys) {
396
+ const keySet = new Set(keys);
397
+ const result = {};
398
+ for (const k of Object.keys(obj)) {
399
+ if (!keySet.has(k)) result[k] = obj[k];
400
+ }
401
+ return result;
402
+ }
403
+
404
+ function flatten(arr) {
405
+ return arr.reduce(function (flat, sub) { return flat.concat(Array.isArray(sub) ? sub : [sub]); }, []);
406
+ }
407
+
408
+ // ===== Date Utilities =====
409
+
410
+ function formatDate(date, format) {
411
+ const d = date instanceof Date ? date : new Date(date);
412
+ if (isNaN(d.getTime())) return 'Invalid Date';
413
+ const pad = function (n) { return n < 10 ? '0' + n : '' + n; };
414
+ const map = {
415
+ YYYY: d.getFullYear(),
416
+ MM: pad(d.getMonth() + 1),
417
+ DD: pad(d.getDate()),
418
+ HH: pad(d.getHours()),
419
+ mm: pad(d.getMinutes()),
420
+ ss: pad(d.getSeconds()),
421
+ };
422
+ let result = format || 'YYYY-MM-DD HH:mm:ss';
423
+ for (const k in map) {
424
+ result = result.replace(k, map[k]);
425
+ }
426
+ return result;
427
+ }
428
+
429
+ function parseDate(str) {
430
+ if (typeof str === 'number') return new Date(str);
431
+ const d = new Date(str);
432
+ if (!isNaN(d.getTime())) return d;
433
+ const m = String(str).match(/^(\\d{4})[-\\/](\\d{1,2})[-\\/](\\d{1,2})/);
434
+ if (m) return new Date(parseInt(m[1], 10), parseInt(m[2], 10) - 1, parseInt(m[3], 10));
435
+ return null;
436
+ }
437
+
438
+ function now() {
439
+ return new Date().toISOString();
440
+ }
441
+
442
+ function daysAgo(n) {
443
+ const d = new Date();
444
+ d.setDate(d.getDate() - n);
445
+ return d;
446
+ }
447
+
448
+ function daysLater(n) {
449
+ const d = new Date();
450
+ d.setDate(d.getDate() + n);
451
+ return d;
452
+ }
453
+
454
+ function dateDiff(d1, d2, unit) {
455
+ const date1 = d1 instanceof Date ? d1 : new Date(d1);
456
+ const date2 = d2 instanceof Date ? d2 : new Date(d2);
457
+ const diffMs = Math.abs(date2.getTime() - date1.getTime());
458
+ unit = unit || 'days';
459
+ switch (unit) {
460
+ case 'seconds': return Math.floor(diffMs / 1000);
461
+ case 'minutes': return Math.floor(diffMs / 60000);
462
+ case 'hours': return Math.floor(diffMs / 3600000);
463
+ case 'days': return Math.floor(diffMs / 86400000);
464
+ default: return diffMs;
465
+ }
466
+ }
467
+
468
+ // ===== String Utilities =====
469
+
470
+ /**
471
+ * template - Simple template interpolation
472
+ * @param {string} str - Template string with {variable} or {a.b.c} placeholders
473
+ * @param {object} data - Data object
474
+ * @returns {string}
475
+ */
476
+ function template(str, data) {
477
+ return str.replace(/\\{([^}]+)\\}/g, function (match, key) {
478
+ const value = key.trim().split('.').reduce(function (obj, k) {
479
+ return obj && obj[k] !== undefined ? obj[k] : undefined;
480
+ }, data);
481
+ return value !== undefined && value !== null ? String(value) : '';
482
+ });
483
+ }
484
+
485
+ function slugify(str) {
486
+ return String(str)
487
+ .toLowerCase()
488
+ .replace(/[^a-z0-9]+/g, '-')
489
+ .replace(/^-+|-+$/g, '');
490
+ }
491
+
492
+ function truncate(str, len, suffix) {
493
+ if (String(str).length <= len) return String(str);
494
+ return String(str).substring(0, len) + (suffix || '...');
495
+ }
496
+
497
+ function pad(str, len, char, right) {
498
+ str = String(str);
499
+ char = char || ' ';
500
+ const diff = len - str.length;
501
+ if (diff <= 0) return str;
502
+ const padding = char.repeat(diff);
503
+ return right ? str + padding : padding + str;
504
+ }
505
+
506
+ // ===== Table Generators =====
507
+
508
+ /**
509
+ * markdownTable - Generate markdown table from array of objects
510
+ * @param {Array<object>} rows - Data rows
511
+ * @param {Array<string>} headers - Optional custom headers (defaults to Object.keys of first row)
512
+ * @returns {string} Markdown table string
513
+ */
514
+ function markdownTable(rows, headers) {
515
+ if (!rows || rows.length === 0) return '';
516
+ headers = headers || Object.keys(rows[0]);
517
+ const lines = [];
518
+ lines.push('| ' + headers.join(' | ') + ' |');
519
+ lines.push('| ' + headers.map(function () { return '---'; }).join(' | ') + ' |');
520
+ for (const row of rows) {
521
+ lines.push('| ' + headers.map(function (h) {
522
+ return String(row[h] == null ? '' : row[h]).replace(/\\|/g, '\\\\|');
523
+ }).join(' | ') + ' |');
524
+ }
525
+ return lines.join('\\n');
526
+ }
527
+
528
+ /**
529
+ * htmlTable - Generate HTML table from array of objects
530
+ * @param {Array<object>} rows - Data rows
531
+ * @param {object} opts - { headers, tableClass }
532
+ * @returns {string} HTML table string
533
+ */
534
+ function htmlTable(rows, opts = {}) {
535
+ if (!rows || rows.length === 0) return '<table><tbody></tbody></table>';
536
+ const headers = opts.headers || Object.keys(rows[0]);
537
+ const tableClass = opts.tableClass ? ' class="' + opts.tableClass + '"' : '';
538
+ const parts = ['<table' + tableClass + '>'];
539
+ parts.push('<thead><tr>');
540
+ for (const h of headers) {
541
+ parts.push('<th>' + h + '</th>');
542
+ }
543
+ parts.push('</tr></thead><tbody>');
544
+ for (const row of rows) {
545
+ parts.push('<tr>');
546
+ for (const h of headers) {
547
+ parts.push('<td>' + String(row[h] == null ? '' : row[h]) + '</td>');
548
+ }
549
+ parts.push('</tr>');
550
+ }
551
+ parts.push('</tbody></table>');
552
+ return parts.join('');
553
+ }
554
+
555
+ // ===== Logger =====
556
+
557
+ const log = {
558
+ info: function (msg, meta) { console.log('[INFO] ' + msg + (meta !== undefined ? ' ' + _safeStringify(meta) : '')); },
559
+ warn: function (msg, meta) { console.warn('[WARN] ' + msg + (meta !== undefined ? ' ' + _safeStringify(meta) : '')); },
560
+ error: function (msg, meta) { console.error('[ERROR] ' + msg + (meta !== undefined ? ' ' + _safeStringify(meta) : '')); },
561
+ debug: function (msg, meta) { console.log('[DEBUG] ' + msg + (meta !== undefined ? ' ' + _safeStringify(meta) : '')); },
562
+ };
563
+
564
+ // ===== Core exports =====
565
+
566
+ const _exports = {
567
+ // HTTP
568
+ fetch: fetch,
569
+ get: get,
570
+ post: post,
571
+ put: put,
572
+ head: head,
573
+ del: del,
574
+ // File I/O
575
+ readJson: readJson,
576
+ writeJson: writeJson,
577
+ readText: readText,
578
+ writeText: writeText,
579
+ appendText: appendText,
580
+ readCsv: readCsv,
581
+ writeCsv: writeCsv,
582
+ exists: exists,
583
+ listDir: listDir,
584
+ // Data
585
+ stats: stats,
586
+ sum: sum,
587
+ avg: avg,
588
+ groupBy: groupBy,
589
+ sortBy: sortBy,
590
+ unique: unique,
591
+ chunk: chunk,
592
+ range: range,
593
+ pick: pick,
594
+ omit: omit,
595
+ flatten: flatten,
596
+ // Date
597
+ formatDate: formatDate,
598
+ parseDate: parseDate,
599
+ now: now,
600
+ daysAgo: daysAgo,
601
+ daysLater: daysLater,
602
+ dateDiff: dateDiff,
603
+ // String
604
+ template: template,
605
+ slugify: slugify,
606
+ truncate: truncate,
607
+ pad: pad,
608
+ // Table
609
+ markdownTable: markdownTable,
610
+ htmlTable: htmlTable,
611
+ // Logger
612
+ log: log,
613
+ // fs/path passthrough for convenience
614
+ fs: fs,
615
+ path: path,
616
+ };
617
+
618
+ module.exports = _exports;
619
+
620
+ // ===== Load extensions from _prelude_ext/ =====
621
+ // 扩展模块通过 module.exports 导出对象,加载时合并进 sb(同名覆盖核心,便于定制)
622
+ (function loadPreludeExtensions() {
623
+ try {
624
+ const extDir = path.join(__dirname, '_prelude_ext');
625
+ if (!fs.existsSync(extDir)) return;
626
+ const extFiles = fs.readdirSync(extDir).filter(function (f) {
627
+ return f.endsWith('.cjs') && f !== 'index.cjs';
628
+ });
629
+ for (const f of extFiles) {
630
+ try {
631
+ const mod = require(path.join(extDir, f));
632
+ const ext = mod && mod.__esModule && mod.default ? mod.default : mod;
633
+ if (ext && typeof ext === 'object') {
634
+ for (const key of Object.keys(ext)) {
635
+ _exports[key] = ext[key];
636
+ }
637
+ }
638
+ } catch (e) {
639
+ console.warn('[prelude] Failed to load extension "' + f + '": ' + (e && e.message));
640
+ }
641
+ }
642
+ } catch (e) { /* ignore extension load errors */ }
643
+ })();
644
+ `;
645
+ //# sourceMappingURL=sandbox-prelude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-prelude.js","sourceRoot":"","sources":["../../../src/sandbox/sandbox-prelude.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAoBH,0EAaC;AAGD,kEAEC;AAGD,sEAEC;AAhCD,MAAM,0BAA0B,GAA8B,EAAE,CAAC;AAEjE;;;;;;GAMG;AACH,SAAgB,+BAA+B,CAAC,IAAY,EAAE,MAAc;IAC1E,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,gCAAgC,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,sCAAsC,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,GAAG,GAAG,0BAA0B,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACzE,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;QACb,0BAA0B,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACrD,CAAC;SAAM,CAAC;QACN,0BAA0B,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED,kBAAkB;AAClB,SAAgB,2BAA2B;IACzC,OAAO,0BAA0B,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,yBAAyB;AACzB,SAAgB,6BAA6B;IAC3C,0BAA0B,CAAC,MAAM,GAAG,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACU,QAAA,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4jBrC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../../src/tool/tools/sandbox.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAW,MAAM,yBAAyB,CAAC;AAKvE,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CA+XrD"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../../src/tool/tools/sandbox.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAW,MAAM,yBAAyB,CAAC;AAKvE,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CA2YrD"}
@@ -61,7 +61,19 @@ function createSandboxTools() {
61
61
  'Supported languages: nodejs, python, shell. ' +
62
62
  'Node.js scripts use CommonJS (.cjs) — `require()` and `module.exports` are supported. ' +
63
63
  'npm packages are NOT pre-installed; use `execute_command` with the preferred package manager from Runtime env (e.g. `pnpm install`, `yarn add`, or `npm install`) with `timeout: 60000`. ' +
64
- 'For PDF generation: prefer Python with `weasyprint` or `reportlab`.',
64
+ 'For PDF generation: prefer Python with `weasyprint` or `reportlab`.\n\n' +
65
+ '**BUILT-IN SANDBOX UTILITIES (global `sb`):**\n' +
66
+ 'A global `sb` object is auto-loaded in all Node.js sandbox scripts. USE IT instead of writing common utilities from scratch:\n' +
67
+ '- **HTTP**: `sb.fetch(url, {method, headers, json, params, timeout})` -> {status, data, text}; `sb.get(url)` / `sb.post(url, body)` shorthands\n' +
68
+ '- **File I/O**: `sb.readJson(file)` / `sb.writeJson(file, data)` / `sb.readText(file)` / `sb.writeText(file, str)` / `sb.readCsv(file)` / `sb.writeCsv(file, rows)` / `sb.exists(path)` / `sb.listDir(dir)`\n' +
69
+ '- **Data**: `sb.stats(arr)` -> {sum, avg, min, max, std, count}; `sb.sum(arr)` / `sb.avg(arr)` / `sb.groupBy(arr, key)` / `sb.sortBy(arr, key, desc)` / `sb.unique(arr, key)` / `sb.chunk(arr, size)` / `sb.range(start, end)` / `sb.pick(obj, keys)` / `sb.flatten(arr)\n' +
70
+ '- **Date**: `sb.formatDate(date, "YYYY-MM-DD")` / `sb.parseDate(str)` / `sb.now()` / `sb.daysAgo(n)` / `sb.daysLater(n)` / `sb.dateDiff(d1, d2, "days")`\n' +
71
+ '- **String**: `sb.template("Hello {name}", {name: "World"})` / `sb.slugify(str)` / `sb.truncate(str, len)` / `sb.pad(str, len, char)\n' +
72
+ '- **Table**: `sb.markdownTable(rows, headers?)` / `sb.htmlTable(rows, {headers?, tableClass?})\n' +
73
+ '- **Logger**: `sb.log.info(msg, meta?)` / `sb.log.warn(msg)` / `sb.log.error(msg)` / `sb.log.debug(msg)\n' +
74
+ '- **Passthrough**: `sb.fs` / `sb.path` (Node.js built-in modules)\n' +
75
+ 'Example: `const data = sb.readJson("input.json"); const grouped = sb.groupBy(data, "category"); const stats = sb.stats(data.map(d => d.value)); sb.log.info("Processed " + data.length + " items");`\n' +
76
+ 'Also available: `require("./_scripts/_json_builder")` for building large JSON data files, and `require("./_scripts/_html_renderer")` for HTML report generation.',
65
77
  parameters: {
66
78
  type: 'object',
67
79
  properties: {